Performance improvements#6634
Conversation
…lated prop changes
…nimation when nothing visually changed.
WalkthroughThis pull request changes animation input stability, refactors axis lifecycle to track previous settings and use replace semantics, adds replace reducers for axes and legend payloads, debounces external and mouse events using per-event RAF scheduling, adjusts SVG utilities, memoizes a graphical item export, updates many tests to use act()/RAF-aware helpers, and adds a new TargetPriceChart example and tests. Changes
Sequence Diagram(s)sequenceDiagram
participant Component
participant Middleware
participant BrowserRAF as Browser (rAF)
participant Handler
rect `#DDEBF7`
note right of Component: Event dispatch (user/DOM)
Component->>Middleware: dispatch externalEventAction(payload)
Middleware->>Middleware: persist reactEvent
Middleware->>BrowserRAF: requestAnimationFrame(callback) -- store rafId by event type
end
rect `#F7F3DD`
note right of BrowserRAF: On next frame
BrowserRAF->>Middleware: invoke callback
Middleware->>Middleware: read latest state (getState())
Middleware->>Handler: call handler(latestState, reactEvent)
Middleware->>Middleware: remove rafId entry for event type
Handler->>Component: dispatches updates -> re-render
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Focus review on:
Possibly related PRs
Suggested labels
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #6634 +/- ##
==========================================
- Coverage 94.15% 94.15% -0.01%
==========================================
Files 493 494 +1
Lines 41104 41458 +354
Branches 4783 4826 +43
==========================================
+ Hits 38700 39033 +333
- Misses 2399 2420 +21
Partials 5 5 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (5)
test/chart/BarChart.spec.tsx (1)
3-3: BarChart tests now correctly synchronize with debounced mouse/tooltip updates
- Importing
actand wrappingvi.runOnlyPendingTimers()inassertActiveBarInteractionsand the chart synchronization tests aligns these specs with the new RAF-based mouse middleware and prevents React act warnings.- The initial
expectinassertActiveBarInteractions(no active bar before hover) is a nice sanity check.For full consistency, you might later also wrap the remaining
vi.advanceTimersByTime(...)calls in this file withact(() => ...), since they now indirectly drive the same debounced mouse/tooltip work.Also applies to: 64-79, 3533-3586, 3614-3630
src/state/externalEventsMiddleware.ts (1)
38-75: State capture inside RAF looks correct; consider defensive cleanupCapturing
stateinside therequestAnimationFramecallback and buildingMouseHandlerDataParamthere ensures handlers see the latest tooltip state, which is exactly what you want with queued mouse events. One small defensive tweak would be to ensure therafIdMapentry is always cleared even if the handler throws:- const rafId = requestAnimationFrame(() => { - /* - * Here it is important that we get the latest state inside the animation frame callback, - * not from the outer scope, because there may have been other actions dispatched - * between the time the event was fired and the animation frame callback is executed. - * One of those actions is the one that actually sets the active tooltip state! - */ - const state: RechartsRootState = listenerApi.getState(); - const nextState: MouseHandlerDataParam = { - activeCoordinate: selectActiveTooltipCoordinate(state), - activeDataKey: selectActiveTooltipDataKey(state), - activeIndex: selectActiveTooltipIndex(state), - activeLabel: selectActiveLabel(state), - activeTooltipIndex: selectActiveTooltipIndex(state), - isTooltipActive: selectIsTooltipActive(state), - }; - - handler(nextState, reactEvent); - - rafIdMap.delete(eventType); - }); + const rafId = requestAnimationFrame(() => { + try { + const state: RechartsRootState = listenerApi.getState(); + const nextState: MouseHandlerDataParam = { + activeCoordinate: selectActiveTooltipCoordinate(state), + activeDataKey: selectActiveTooltipDataKey(state), + activeIndex: selectActiveTooltipIndex(state), + activeLabel: selectActiveLabel(state), + activeTooltipIndex: selectActiveTooltipIndex(state), + isTooltipActive: selectIsTooltipActive(state), + }; + + handler(nextState, reactEvent); + } finally { + rafIdMap.delete(eventType); + } + });Functionally you’re already fine, but this closes a small edge case.
src/state/SetLegendPayload.ts (1)
11-23: Legend payload lifecycle is well handled; note referential equality assumptionThe
prevPayloadRef+useLayoutEffectpattern cleanly switches legend updates from add+remove to add+replace+remove in both cartesian and polar cases, and the panorama/layout guards avoid unnecessary work.One minor implication: the
replaceLegendPayloadbranch is driven purely by referential equality (prevPayloadRef.current !== legendPayload). That’s fine given theReadonlyArray<LegendPayload>type, but if callers ever mutate arrays in place instead of passing new references, changes won’t be detected. If that’s a concern, consider documenting the immutability expectation or doing a shallow equality check instead.Also applies to: 25-32, 40-52, 54-61
www/src/docs/exampleComponents/ComposedChart/TargetPriceChart.tsx (2)
138-153: Handle null/undefined activeTooltipIndex instead of coercing to a number
useActiveIndexStatecurrently coercesactiveTooltipIndexwithNumber(...), which will turnundefinedintoNaNandnullinto0. That can briefly highlight the wrong index or leaveactiveIndexin aNaNstate.You can keep the API the same but treat “no active tooltip” as
undefined:- const setActiveIndex = useCallback( - (props: MouseHandlerDataParam) => { - setState(Number(props.activeTooltipIndex)); - }, - [setState], - ); + const setActiveIndex = useCallback( + (props: MouseHandlerDataParam) => { + const index = props.activeTooltipIndex; + setState(index == null ? undefined : Number(index)); + }, + [setState], + );This keeps
activeIndexunset when there’s no meaningful tooltip index.
167-183: Optional: derive X-axis domain from data to avoid future drift
domain={[startingTimestamp, endingTimestamp]}is currently kept in sync withchartDatavia exported constants. If the sample data is edited later, it’s easy to forget updating these exports.If you want to make the example more self-maintaining, you could instead derive the domain directly from
chartDataor use'dataMin' | 'dataMax':- domain={[startingTimestamp, endingTimestamp]} + domain={['dataMin', 'dataMax']}or
- domain={[startingTimestamp, endingTimestamp]} + domain={[chartData[0]?.timestamp, chartData[chartData.length - 1]?.timestamp]}Not required, but simplifies future tweaks to the dataset.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (4)
test-vr/__snapshots__/tests/www/ComposedChartApiExamples.spec-vr.tsx-snapshots/TargetPriceChart-1-chromium-linux.pngis excluded by!**/*.pngtest-vr/__snapshots__/tests/www/ComposedChartApiExamples.spec-vr.tsx-snapshots/TargetPriceChart-1-firefox-linux.pngis excluded by!**/*.pngtest-vr/__snapshots__/tests/www/ComposedChartApiExamples.spec-vr.tsx-snapshots/TargetPriceChart-1-webkit-linux.pngis excluded by!**/*.pngwww/test/__snapshots__/navigation.spec.ts.snapis excluded by!**/*.snap
📒 Files selected for processing (37)
src/cartesian/Area.tsx(2 hunks)src/cartesian/Line.tsx(1 hunks)src/cartesian/XAxis.tsx(2 hunks)src/cartesian/YAxis.tsx(2 hunks)src/cartesian/ZAxis.tsx(1 hunks)src/state/SetGraphicalItem.ts(3 hunks)src/state/SetLegendPayload.ts(1 hunks)src/state/cartesianAxisSlice.ts(4 hunks)src/state/externalEventsMiddleware.ts(1 hunks)src/state/legendSlice.ts(2 hunks)src/state/mouseEventsMiddleware.ts(1 hunks)src/state/reduxDevtoolsJsonStringifyReplacer.ts(1 hunks)src/util/svgPropertiesAndEvents.ts(1 hunks)src/util/svgPropertiesNoEvents.ts(2 hunks)test-vr/tests/www/ComposedChartApiExamples.spec-vr.tsx(1 hunks)test/README.md(1 hunks)test/cartesian/XAxis.spec.tsx(2 hunks)test/cartesian/YAxis/YAxis.spec.tsx(2 hunks)test/chart/AreaChart.spec.tsx(4 hunks)test/chart/BarChart.spec.tsx(5 hunks)test/chart/CategoricalChart.spec.tsx(4 hunks)test/chart/ComposedChart.spec.tsx(2 hunks)test/chart/FunnelChart.spec.tsx(2 hunks)test/chart/LineChart.spec.tsx(4 hunks)test/chart/PieChart.spec.tsx(4 hunks)test/chart/RadialBarChart.spec.tsx(2 hunks)test/chart/RechartsWrapper.spec.tsx(2 hunks)test/chart/chartEvents.spec.tsx(8 hunks)test/component/Tooltip/Tooltip.visibility.spec.tsx(4 hunks)test/component/Tooltip/tooltipEventType.spec.tsx(3 hunks)test/component/Tooltip/tooltipTestHelpers.tsx(2 hunks)test/helper/selectorRecalculationDebugger.ts(1 hunks)test/state/externalEventsMiddleware.spec.ts(1 hunks)test/state/mouseEventsMiddleware.spec.ts(1 hunks)www/src/docs/exampleComponents/ComposedChart/TargetPriceChart.tsx(1 hunks)www/src/docs/exampleComponents/ComposedChart/index.ts(2 hunks)www/src/navigation.data.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (23)
src/cartesian/Line.tsx (1)
src/util/useAnimationId.tsx (1)
useAnimationId(18-28)
src/state/mouseEventsMiddleware.ts (6)
src/util/types.ts (1)
MousePointer(1056-1060)src/state/store.ts (2)
RechartsRootState(94-94)AppDispatch(95-95)src/util/getChartPointer.ts (1)
getChartPointer(16-34)src/state/selectors/selectTooltipEventType.ts (1)
selectTooltipEventType(27-31)src/state/selectors/selectActivePropsFromChartPointer.ts (1)
selectActivePropsFromChartPointer(14-29)src/state/tooltipSlice.ts (2)
setMouseOverAxisIndex(328-335)mouseLeaveChart(306-316)
src/cartesian/Area.tsx (1)
src/util/useAnimationId.tsx (1)
useAnimationId(18-28)
test/chart/PieChart.spec.tsx (1)
storybook/stories/API/props/EventHandlers.ts (2)
onMouseEnter(106-106)onMouseLeave(107-107)
src/state/externalEventsMiddleware.ts (3)
src/state/store.ts (2)
RechartsRootState(94-94)AppDispatch(95-95)src/synchronisation/types.ts (1)
MouseHandlerDataParam(4-20)src/state/selectors/tooltipSelectors.ts (5)
selectActiveTooltipCoordinate(435-444)selectActiveTooltipDataKey(405-414)selectActiveTooltipIndex(395-398)selectActiveLabel(400-403)selectIsTooltipActive(446-449)
src/state/legendSlice.ts (1)
src/component/DefaultLegendContent.tsx (1)
LegendPayload(26-42)
test/chart/BarChart.spec.tsx (1)
test/component/Tooltip/tooltipTestHelpers.tsx (1)
expectTooltipPayload(140-152)
src/state/SetLegendPayload.ts (4)
src/component/DefaultLegendContent.tsx (1)
LegendPayload(26-42)src/state/hooks.ts (2)
useAppDispatch(9-15)useAppSelector(40-50)src/context/PanoramaContext.tsx (1)
useIsPanorama(6-6)src/context/chartLayoutContext.tsx (1)
selectChartLayout(133-133)
www/src/docs/exampleComponents/ComposedChart/index.ts (1)
www/src/docs/exampleComponents/ComposedChart/TargetPriceChart.tsx (1)
TargetPriceChart(157-224)
test/chart/CategoricalChart.spec.tsx (1)
test/helper/createSelectorTestCase.tsx (1)
rechartsTestRender(172-184)
src/util/svgPropertiesAndEvents.ts (2)
src/util/svgPropertiesNoEvents.ts (2)
isSvgElementPropKey(318-323)isDataAttribute(334-336)src/util/excludeEventProps.ts (1)
isEventKey(167-173)
src/cartesian/XAxis.tsx (1)
src/state/cartesianAxisSlice.ts (1)
XAxisSettings(74-78)
test/component/Tooltip/Tooltip.visibility.spec.tsx (3)
src/component/TooltipBoundingBox.tsx (1)
render(78-145)test/component/Tooltip/tooltipTestHelpers.tsx (1)
showTooltipOnCoordinate(78-85)test/component/Tooltip/tooltipMouseHoverSelectors.ts (1)
lineChartMouseHoverTooltipSelector(26-26)
test/cartesian/XAxis.spec.tsx (1)
test/helper/createSelectorTestCase.tsx (1)
rechartsTestRender(172-184)
test/component/Tooltip/tooltipEventType.spec.tsx (2)
test/component/Tooltip/tooltipTestHelpers.tsx (2)
showTooltip(120-122)hideTooltip(124-128)test/component/Tooltip/tooltipMouseHoverSelectors.ts (1)
barChartMouseHoverTooltipSelector(18-18)
src/state/SetGraphicalItem.ts (1)
src/state/graphicalItemsSlice.ts (1)
CartesianGraphicalItemSettings(59-59)
test/cartesian/YAxis/YAxis.spec.tsx (1)
test/helper/createSelectorTestCase.tsx (1)
rechartsTestRender(172-184)
test-vr/tests/www/ComposedChartApiExamples.spec-vr.tsx (1)
www/src/docs/exampleComponents/ComposedChart/TargetPriceChart.tsx (1)
TargetPriceChart(157-224)
src/cartesian/ZAxis.tsx (2)
src/state/cartesianAxisSlice.ts (1)
ZAxisSettings(93-95)src/state/hooks.ts (1)
useAppDispatch(9-15)
www/src/docs/exampleComponents/ComposedChart/TargetPriceChart.tsx (1)
storybook/stories/API/props/AnimationProps.ts (1)
isAnimationActive(29-36)
test/state/mouseEventsMiddleware.spec.ts (4)
src/util/types.ts (1)
MousePointer(1056-1060)test/helper/mockGetBoundingClientRect.ts (1)
getMockDomRect(5-20)src/state/store.ts (1)
createRechartsStore(40-92)src/state/mouseEventsMiddleware.ts (2)
mouseClickAction(10-10)mouseMoveAction(32-32)
src/cartesian/YAxis.tsx (1)
src/state/cartesianAxisSlice.ts (1)
YAxisSettings(82-87)
test/state/externalEventsMiddleware.spec.ts (2)
src/state/store.ts (2)
RechartsRootState(94-94)createRechartsStore(40-92)src/state/externalEventsMiddleware.ts (1)
externalEventAction(19-19)
🪛 LanguageTool
test/README.md
[style] ~99-~99: ‘take into account’ might be wordy. Consider a shorter alternative.
Context: ...t tooltips appear on hover, you need to take into account that the mouse move middleware is hidde...
(EN_WORDINESS_PREMIUM_TAKE_INTO_ACCOUNT)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build, Test, Pack
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (33)
test/helper/selectorRecalculationDebugger.ts (1)
7-8: Documentation clarification improves API usability.The example is now clearer, showing the simplified and correct usage pattern: pass only the selector name to create a debug function, then invoke that function with the dependency object inside your selector. This aligns with the actual API and makes the utility more approachable for developers.
src/state/reduxDevtoolsJsonStringifyReplacer.ts (2)
1-1: LGTM! Parameter rename is correct.The rename from
_keytokeyis appropriate since the parameter is now actively used in the children filtering logic below.
8-10: LGTM! Children filtering improves DevTools performance.The addition correctly prevents serialization of complex React element trees when the key is 'children'. The null check is necessary since
typeof null === 'object'in JavaScript, and the logic appropriately handles both object and array children types.src/util/svgPropertiesNoEvents.ts (3)
316-317: LGTM: Excellent Set-based optimization.Creating a Set from the property keys array enables O(1) lookup performance instead of O(n), a clear win for the repeated lookups in
isSvgElementPropKey.
322-322: LGTM: Correct Set lookup implementation.The Set.has() method provides the intended O(1) lookup performance.
344-356: LGTM: Clean for...in optimization with defensive guards.The manual property iteration is significantly faster than Object.entries + filter + fromEntries. The guard correctly handles non-object inputs (including null, since
typeof null === 'object'), and the hasOwnProperty check safely filters inherited properties.src/util/svgPropertiesAndEvents.ts (1)
20-30: LGTM: Consistent for...in optimization.This implementation mirrors the same performance optimization pattern used in
svgPropertiesNoEvents, replacing the Object.entries-based approach with a direct for...in loop. The comment documenting the 10x performance improvement is helpful context.src/cartesian/Area.tsx (1)
460-461: LGTM! Animation input correctly memoized.The memoization prevents creating a new object reference on every render, ensuring
useAnimationIdonly generates a new ID whenpointsorbaseLineactually change. This aligns well with the PR's goal of limiting unnecessary animation triggers.src/cartesian/Line.tsx (1)
379-379: Selector is properly memoized—no changes needed.The
selectLinePointsselector usescreateSelectorfrom reselect, which provides built-in memoization. It returns the same array reference as long as its dependencies remain unchanged, satisfying the reference equality check required byuseAnimationId. The animation ID dependency is stable and correct.src/state/SetGraphicalItem.ts (2)
1-1: LGTM: Import addition supports memoization.The addition of
memoto the React imports is necessary and correct for the memoization applied on line 51.
13-49: LGTM: Internal implementation preserves original logic.The refactoring of the original function to an internal implementation is clean and preserves all the original behavior, including the StrictMode-aware cleanup logic.
test/chart/RechartsWrapper.spec.tsx (1)
3-3: LGTM! Proper test synchronization with RAF-batched events.The addition of
act()blocks around timer flushing correctly synchronizes test assertions with the RAF-batched mouse event handlers introduced in this PR. This follows React Testing Library best practices.Also applies to: 24-26, 33-35
test/chart/RadialBarChart.spec.tsx (1)
2-2: LGTM! Consistent test synchronization pattern.The
act()wrappers around timer flushing aftermouseOverevents ensure that active shape updates are processed before assertions, aligning with the RAF-batched event handling.Also applies to: 19-29
test/chart/FunnelChart.spec.tsx (1)
2-2: LGTM! Event synchronization properly applied.The
act()wrapper ensures all event types (click, mouseEnter, mouseMove, mouseLeave) are processed through the RAF queue before assertions execute.Also applies to: 100-102
test/chart/chartEvents.spec.tsx (1)
2-2: LGTM! Comprehensive event synchronization coverage.Excellent test coverage with
act()blocks applied consistently across all event types (mouse, touch, click). The addition of the pre-interaction assertion at line 240 is good practice, verifying the initial state before the event fires.Also applies to: 70-73, 87-104, 120-141, 213-215, 240-270
test-vr/tests/www/ComposedChartApiExamples.spec-vr.tsx (1)
12-17: LGTM! VR test coverage for new example component.The test properly disables animation for deterministic screenshot comparison and sets a specific
defaultIndexto capture a meaningful state of the chart.www/src/navigation.data.ts (1)
122-122: LGTM! New example registered in navigation.The TargetPriceChart is properly added to the example components list, making it accessible in the documentation UI.
test/README.md (1)
97-102: LGTM! Clear documentation of tooltip testing pattern.The new section clearly explains the RAF-based middleware behavior and provides practical solutions for testing tooltips. This will help developers write correct tests that synchronize with the new async event handling.
test/chart/ComposedChart.spec.tsx (1)
2-2: LGTM! Correct event type and proper synchronization.The change from
mouseOvertomouseEnteris semantically more appropriate for chart interaction testing, and theact()wrapper ensures proper synchronization with RAF-batched event handling.Also applies to: 72-76
test/component/Tooltip/tooltipTestHelpers.tsx (1)
2-2: Wrapping timer flush inactlooks correctUsing
act(() => vi.runOnlyPendingTimers())here is a good match for the new RAF-based tooltip behavior and avoids act warnings while keeping semantics identical.If any tests call these helpers without
vi.useFakeTimers()(or an equivalent global setup),vi.runOnlyPendingTimers()will throw—please confirm timers are always faked before using these helpers.Also applies to: 54-60
test/chart/AreaChart.spec.tsx (1)
1-1: Timer flush wrapped inactis appropriate for RAF‑driven updatesThe new
act(() => vi.runOnlyPendingTimers())calls after mouse interactions correctly synchronize tests with debounced tooltip/active-dot updates.Please ensure this suite (or global test setup) enables fake timers before calling
vi.runOnlyPendingTimers(), otherwise Vitest will error when these tests run.Also applies to: 99-101, 125-127, 761-763
test/cartesian/XAxis.spec.tsx (1)
52-52: UsingrechartsTestRenderfor the XAxis removal test is a good fitSwitching to
rechartsTestRenderkeeps the same chart/store instance across rerenders, which matches the intent of verifying config removal from the store while simplifying the test API.Also applies to: 2035-2035
test/chart/CategoricalChart.spec.tsx (1)
3-3: ReusingrechartsTestRenderand syncing mouse events viaactmakes senseRendering through
rechartsTestRenderand then flushing pending timers insideactafter the click/dblclick/contextmenu sequence matches the new debounced event handling and keeps the test harness consistent with other suites.As with the other updated tests, please double‑check that fake timers are enabled before
vi.runOnlyPendingTimers()is called in this file (either locally or via global setup).Also applies to: 25-25, 189-189, 193-199, 207-209
src/state/mouseEventsMiddleware.ts (1)
36-45: RAF debouncing is good, but sharedrafIdmay misbehave with multiple chartsThe new debounced mouse-move handling is a solid perf win: you cancel the previous frame, compute
chartPointeronce, and only dispatch axis tooltip updates once per animation frame for the latest pointer position.The risky bit is the module‑level
rafId:
- If a single
mouseMoveMiddlewareinstance is ever attached to more than one Redux store (i.e., more than one chart sharing this middleware object), mouse moves in chart A can cancel the scheduled RAF for chart B, so only one chart’s axis state is updated per frame.- The comment claims “each chart has its own Redux store instance with its own middleware”; that’s only safe if each store gets its own listener middleware instance (and thus its own
rafId) rather than all stores importing and reusing this singleton.Please verify how
mouseMoveMiddlewareis wired instore.ts:
- If the same exported
mouseMoveMiddleware.middlewareis reused across multiple chart stores, consider scopingrafIdper store (e.g., by creating the listener middleware inside the store factory) or keeping a per‑storerafIdmap instead of a single shared variable.- If each chart truly gets its own middleware instance, it would be helpful to document that invariant here so future refactors don’t accidentally introduce cross‑chart interference.
Also applies to: 51-79
src/state/legendSlice.ts (1)
65-83: Legend payload swap strategy looks solidThanks for introducing the replace path that mirrors the axis slices—keeping the list stable while swapping the contents should cut down on needless add/remove churn, and the
currentlookup matches the existing removal pattern nicely.src/cartesian/YAxis.tsx (1)
57-78: Lower churn on YAxis registrationsCaching the previous settings and leaning on
replaceYAxiskeeps the registration stable across renders, so we sidestep the old add/remove churn and the downstream selector invalidations. Nice clean improvement.test/component/Tooltip/tooltipEventType.spec.tsx (1)
161-168: LGTM! Test helper usage improves consistency.The refactoring to use
showTooltipandhideTooltiphelpers instead of direct DOM events improves test maintainability and ensures consistent RAF timing handling across the test suite.test/component/Tooltip/Tooltip.visibility.spec.tsx (2)
432-434: LGTM! Proper act() usage for timer advancement.Wrapping
vi.runOnlyPendingTimers()inact()ensures React processes all state updates triggered by the RAF callback before assertions run. This is the correct pattern for testing RAF-based debouncing.
1583-1591: LGTM! Helper usage with debug support.The refactoring to use
showTooltipOnCoordinatewith the optionaldebugparameter improves test maintainability and provides better debugging capability when tests fail.test/state/mouseEventsMiddleware.spec.ts (1)
1-81: LGTM! Comprehensive middleware timing tests.This test suite effectively validates the key performance optimization:
- Click events process synchronously without RAF overhead
- Mouse move events debounce via RAF, canceling previous pending frames when new moves arrive
- Proper use of
act()ensures timer execution is synchronized with ReactThe test expectations align with the PR's RAF-based debouncing strategy for mouse movements.
test/state/externalEventsMiddleware.spec.ts (1)
1-183: LGTM! Excellent test coverage of RAF scheduling semantics.This comprehensive test suite validates:
- Handler invocation timing and payload shape
- React 16 compatibility via
event.persist()- Per-event-type RAF scheduling (different event types have independent timers)
- Proper cancellation when same event type is dispatched multiple times
The test expectations clearly document the middleware's behavior and provide confidence in the RAF-based batching implementation.
www/src/docs/exampleComponents/ComposedChart/index.ts (1)
15-16: LGTM! New example follows established pattern.The TargetPriceChart example is correctly integrated into the examples registry, following the same structure as existing entries.
Also applies to: 51-55
src/state/externalEventsMiddleware.ts (1)
23-31: Per-event-type RAF map is a solid improvementUsing
rafIdMapkeyed byeventTypeprevents different DOM event types from cancelling each other’s frames while still allowing debouncing per type. This aligns well with the middleware’s multi-event responsibilities.
Bundle ReportChanges will increase total bundle size by 15.77kB (0.61%) ⬆️. This is within the configured threshold ✅ Detailed changes
Affected Assets, Files, and Routes:view changes for bundle: recharts/bundle-cjsAssets Changed:
view changes for bundle: recharts/bundle-es6Assets Changed:
view changes for bundle: recharts/bundle-umdAssets Changed:
|
|
Visual Regression tests failed.
To update snapshots: Comment |
| const result: Record<PropertyKey, any> = {}; | ||
| // for ... in loop is 10x faster than Object.entries + filter + Object.fromEntries in Chrome | ||
| // eslint-disable-next-line no-restricted-syntax | ||
| for (const key in obj) { |
) ## Description We already had a nice handing for when an animation is interrupted by a change in the data. We did not have anything for when an animation is interrupted by a render that does not change the data which is what #6044 is about. ## Related Issue Fixes #6044 Some improvements are already in main since #6634 ## Screenshots (if appropriate): This is what it looked like before #6634, same as in the original report in #6044: https://github.com/user-attachments/assets/d477a374-c216-4411-8b12-47d0b8ed95f7 This is what is currently in the main branch, notice how the length suddenly jumps: https://github.com/user-attachments/assets/f4adab16-a884-4c76-b896-b6ee0723dd53 After this PR: https://github.com/user-attachments/assets/469c4fb5-22c2-461f-9613-589fcff5b0ce ## Types of changes - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) ## Checklist: - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [x] I have added tests to cover my changes. - [x] I have added a storybook story or VR test, or extended an existing story or VR test to show my changes <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Fixed line animations continuing from their previous position when strokeWidth or other properties change during playback, eliminating unwanted animation restarts. * **Tests** * Added test coverage for property changes occurring during line animation. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
Description
Mostly just focused on not doing stuff that is not necessary.
Object.entriescall intofor ... in loop, turns out that improves the performance 10x, who knew.nullinstead of zero-opacity text. I think these are things we can reasonably ask from people who aim to render thousands of SVG nodes at 60fps.JavascriptAnimate,Label, andCartesianGridcomponents but I did not find any performance impact whatsoever so I left those as-is.Related Issue
I think this quite significantly helps with #6340. I dare to say it even fixes!
I am going to test if this has any impact on #6044 edit: it helps significantly but it's not a complete fix
I will also test impact on #6624 edit: not much changed, this case needs a different approach
Motivation and Context
60fps should be enough for everyone
How Has This Been Tested?
Devtools, I don't have automated tests for this unfortunately.
Screenshots (if appropriate):
Here is comparison to Recharts v2 on 6x slowdown. Notice that v2 comes with throttle built-in somehow, my branch was without throttle. I think this is about even BUT BONUS the latest variant can keep up with the label rendering too:
https://github.com/user-attachments/assets/bebe3e98-ee7a-443d-98cc-60ee08760233
Comparison to Recharts v3 on 6x slowdown. I think we can agree there is some improvement:
https://github.com/user-attachments/assets/39b591d2-ee81-44db-9072-38c8685b61cc
If you wish to replicate the v2 throttle you can do that too, here is a comparison on 20x slowdown. I think these are about even?
https://github.com/user-attachments/assets/8601c8f9-b658-48d4-97b4-626617d77c8b
Here is an example of what the throttle could look like:
Here is the SVG calculation change, before (400+ms) and after (30+ms):


Types of changes
Checklist:
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
Tests