Skip to content

Optimize SetTooltipEntrySettings to break infinite rendering loop#6616

Merged
ckifer merged 2 commits intomainfrom
infiniteloop
Nov 13, 2025
Merged

Optimize SetTooltipEntrySettings to break infinite rendering loop#6616
ckifer merged 2 commits intomainfrom
infiniteloop

Conversation

@PavelVanecek
Copy link
Collaborator

@PavelVanecek PavelVanecek commented Nov 13, 2025

Description

Specifying activeDot as a new object every render is not best practice but we can still do this little thing to break this particular render loop.

Related Issue

Fixes #6613

Types of changes

  • 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.
  • I have added tests to cover my changes.
  • I have added a storybook story or VR test, or extended an existing story or VR test to show my changes

Summary by CodeRabbit

  • Bug Fixes

    • Resolved an infinite loop issue when using inline object styling for active dots on line charts.
    • Enhanced tooltip entry settings management to properly replace existing entries instead of duplicating them.
  • Tests

    • Added test coverage for inline object active dot configurations.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 13, 2025

Walkthrough

This PR introduces a tooltip settings replacement mechanism to prevent infinite re-renders. The system now tracks previous tooltip configurations and replaces existing entries when settings change, rather than continuously adding new ones. A new test validates the fix prevents infinite loops.

Changes

Cohort / File(s) Summary
Tooltip State Management
src/state/SetTooltipEntrySettings.tsx, src/state/tooltipSlice.ts
Added useRef tracking for previous tooltip settings. Changed dispatch logic to call replaceTooltipEntrySettings on subsequent renders when settings differ, and removeTooltipEntrySettings on cleanup. Introduced new replaceTooltipEntrySettings reducer that finds and updates existing tooltip entries instead of adding duplicates.
Line Chart Rendering
src/cartesian/Line.tsx
Updated getTooltipEntrySettings to destructure tooltipType from props and use local variable for type field. Changed color source from props.stroke to local stroke variable.
Test Coverage
test/hooks/useActiveTooltipDataPoints.spec.tsx
Added new test scenario for inline activeDot object on Line component to verify tooltip interaction does not trigger infinite re-render loops.

Sequence Diagram

sequenceDiagram
    participant Render as Component Render
    participant Layout as Layout Effect
    participant Store as Redux Store
    participant Ref as prevSettingsRef

    Note over Render,Ref: Previous Flow (Infinite Loop)
    Render->>Layout: Re-render with new settings
    Layout->>Store: Dispatch addTooltipEntrySettings
    Store->>Render: Update state (re-render triggered)
    Render->>Layout: Re-render with new settings
    Layout->>Store: Dispatch addTooltipEntrySettings again
    Note over Layout,Store: ⚠️ Infinite cycle

    rect rgb(240, 250, 240)
    Note over Render,Ref: New Flow (Fixed)
    Render->>Layout: Re-render with new settings
    Layout->>Ref: Read prevSettingsRef
    alt First Render
        Layout->>Store: Dispatch addTooltipEntrySettings
    else Subsequent Render with Different Settings
        Layout->>Store: Dispatch replaceTooltipEntrySettings {prev, next}
    else Settings Unchanged
        Note over Layout: No dispatch
    end
    Ref->>Ref: Update prevSettingsRef
    Layout->>Store: Update succeeded ✓
    Render->>Render: Prevent re-triggering
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Areas requiring extra attention:

  • SetTooltipEntrySettings.tsx — Verify the comparison logic between prev and next settings correctly identifies changes and that the ref tracking prevents stale closures
  • tooltipSlice.ts — Ensure the replaceTooltipEntrySettings reducer correctly locates the matching entry and that castDraft and current(state) are used appropriately for Immer immutability
  • Test scenario — Confirm the test adequately reproduces the original infinite-loop condition and validates the fix without false negatives

Suggested labels

enhancement

Suggested reviewers

  • ckifer

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main optimization: breaking an infinite rendering loop in SetTooltipEntrySettings, matching the core change in the changeset.
Description check ✅ Passed The description provides motivation, links the related issue, and checks off relevant checklist items, though it lacks specific testing details.
Linked Issues check ✅ Passed The changes address the infinite re-render issue by tracking previous tooltip settings and using replaceTooltipEntrySettings to prevent duplicate state updates, directly fixing issue #6613.
Out of Scope Changes check ✅ Passed All changes are focused on fixing the infinite render loop: modifications to SetTooltipEntrySettings, addition of replaceTooltipEntrySettings reducer, Line.tsx updates for consistency, and test coverage for the fix.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch infiniteloop

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0c3ffe3 and 95eb39f.

📒 Files selected for processing (4)
  • src/cartesian/Line.tsx (2 hunks)
  • src/state/SetTooltipEntrySettings.tsx (2 hunks)
  • src/state/tooltipSlice.ts (2 hunks)
  • test/hooks/useActiveTooltipDataPoints.spec.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Do not hardcode any user-facing strings or formatting choices in the library code; leave internationalization to library users

Files:

  • src/cartesian/Line.tsx
  • src/state/tooltipSlice.ts
  • src/state/SetTooltipEntrySettings.tsx
🧠 Learnings (4)
📚 Learning: 2025-10-25T07:35:46.188Z
Learnt from: CR
Repo: recharts/recharts PR: 0
File: test/README.md:0-0
Timestamp: 2025-10-25T07:35:46.188Z
Learning: Applies to test/**/*.test.{ts,tsx} : Mock getBoundingClientRect using test/helper/MockGetBoundingClientRect.ts when rendering Recharts components (e.g., Tooltip, Legend, charts)

Applied to files:

  • test/hooks/useActiveTooltipDataPoints.spec.tsx
📚 Learning: 2025-10-25T07:35:46.188Z
Learnt from: CR
Repo: recharts/recharts PR: 0
File: test/README.md:0-0
Timestamp: 2025-10-25T07:35:46.188Z
Learning: Applies to test/**/*.test.{ts,tsx} : When testing selectors, verify render counts using the spy and rerenderSameComponent from createSelectorTestCase

Applied to files:

  • test/hooks/useActiveTooltipDataPoints.spec.tsx
📚 Learning: 2025-10-25T07:35:46.188Z
Learnt from: CR
Repo: recharts/recharts PR: 0
File: test/README.md:0-0
Timestamp: 2025-10-25T07:35:46.188Z
Learning: Applies to test/**/*.test.{ts,tsx} : Prefer using the createSelectorTestCase helper when writing or modifying tests

Applied to files:

  • test/hooks/useActiveTooltipDataPoints.spec.tsx
📚 Learning: 2025-10-25T07:36:02.229Z
Learnt from: CR
Repo: recharts/recharts PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-25T07:36:02.229Z
Learning: Recharts aims for simple, declarative, and composable charts; prioritize consistency, usability, performance, and accessibility

Applied to files:

  • test/hooks/useActiveTooltipDataPoints.spec.tsx
🧬 Code graph analysis (3)
src/cartesian/Line.tsx (1)
storybook/stories/API/props/Tooltip.ts (1)
  • tooltipType (3-8)
test/hooks/useActiveTooltipDataPoints.spec.tsx (5)
src/hooks.ts (1)
  • useActiveTooltipDataPoints (73-75)
test/helper/createSelectorTestCase.tsx (1)
  • createSelectorTestCase (78-139)
test/_data.ts (1)
  • PageData (4-11)
test/component/Tooltip/tooltipTestHelpers.tsx (1)
  • showTooltip (113-115)
test/component/Tooltip/tooltipMouseHoverSelectors.ts (1)
  • composedChartMouseHoverTooltipSelector (27-27)
src/state/SetTooltipEntrySettings.tsx (1)
src/state/tooltipSlice.ts (1)
  • TooltipPayloadConfiguration (52-69)
⏰ 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 (5)
src/cartesian/Line.tsx (1)

176-194: LGTM! Consistent destructuring pattern.

The changes improve code consistency by destructuring tooltipType from props and using local variables instead of accessing via props.*. This aligns with the existing pattern for other properties like stroke.

test/hooks/useActiveTooltipDataPoints.spec.tsx (1)

351-379: LGTM! Test effectively validates the infinite loop fix.

The test directly addresses issue #6613 by rendering a Line with an inline activeDot object (which creates a new object reference on every render). If the infinite loop regression occurs, this test would timeout or fail. The test structure follows established patterns and uses the createSelectorTestCase helper as recommended.

Based on learnings

src/state/tooltipSlice.ts (1)

274-285: LGTM! Reducer correctly replaces tooltip entry settings.

The reducer uses reference equality via indexOf to locate the previous configuration, which works correctly since SetTooltipEntrySettings.tsx maintains the exact reference in prevSettingsRef. The use of current(state) and castDraft is appropriate for Immer's draft state handling, and the conditional update (index > -1) gracefully handles edge cases.

src/state/SetTooltipEntrySettings.tsx (2)

21-33: LGTM! Core fix successfully prevents infinite loop.

The effect correctly implements the optimization:

  1. First render: Dispatches addTooltipEntrySettings and stores the reference
  2. Subsequent renders: Dispatches replaceTooltipEntrySettings to update in-place rather than adding new entries

By replacing instead of accumulating entries, the tooltip payload array maintains constant size even when props like activeDot are new objects on every render. This breaks the infinite loop described in issue #6613.

The comparison at line 29 (prevSettingsRef.current !== tooltipEntrySettings) will always be true since fn(args) creates a new object each time, but this is intentional—the replacement ensures we update with the latest settings while maintaining array size.


35-42: LGTM! Cleanup effect correctly handles unmounting.

The separate cleanup effect with [dispatch] dependencies is the right approach. This ensures:

  • The cleanup only runs on unmount (or when dispatch changes, which is rare)
  • When fn, args, or isPanorama change, we replace the entry in-place rather than removing and re-adding it
  • The last known prevSettingsRef.current is properly cleaned up on unmount

If this cleanup were combined with the main effect, it would trigger unnecessary remove/add cycles on every prop change, potentially causing flickering or performance issues.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Nov 13, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.16%. Comparing base (0c3ffe3) to head (95eb39f).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #6616   +/-   ##
=======================================
  Coverage   94.15%   94.16%           
=======================================
  Files         493      493           
  Lines       41081    41105   +24     
  Branches     4773     4780    +7     
=======================================
+ Hits        38681    38705   +24     
  Misses       2395     2395           
  Partials        5        5           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@codecov
Copy link

codecov bot commented Nov 13, 2025

Bundle Report

Changes will increase total bundle size by 2.87kB (0.11%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
recharts/bundle-cjs 1.12MB 1.29kB (0.12%) ⬆️
recharts/bundle-es6 968.59kB 1.1kB (0.11%) ⬆️
recharts/bundle-umd 509.99kB 472 bytes (0.09%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: recharts/bundle-umd

Assets Changed:

Asset Name Size Change Total Size Change (%)
Recharts.js 472 bytes 509.99kB 0.09%
view changes for bundle: recharts/bundle-cjs

Assets Changed:

Asset Name Size Change Total Size Change (%)
cartesian/Line.js 5 bytes 24.46kB 0.02%
state/tooltipSlice.js 780 bytes 8.79kB 9.74% ⚠️
state/SetTooltipEntrySettings.js 506 bytes 1.46kB 53.21% ⚠️
view changes for bundle: recharts/bundle-es6

Assets Changed:

Asset Name Size Change Total Size Change (%)
cartesian/Line.js 5 bytes 22.94kB 0.02%
state/tooltipSlice.js 599 bytes 7.4kB 8.81% ⚠️
state/SetTooltipEntrySettings.js 500 bytes 1.29kB 63.21% ⚠️

@ckifer ckifer merged commit 61099c0 into main Nov 13, 2025
27 of 29 checks passed
@ckifer ckifer deleted the infiniteloop branch November 13, 2025 15:28
This was referenced Nov 15, 2025
PavelVanecek added a commit that referenced this pull request Nov 16, 2025
## Description

We had two visual diff tests fail:
https://www.chromatic.com/build?appId=63da8268a0da9970db6992aa&number=3175

The first one was trivial: the default was declared after the explicit
prop and was overwriting it.

Second was more complicated. I made a mistake in
#6616 in the comparator logic:
it was looking up a tooltip settings by reference, which it could never
find because it had created a new reference in the same reducer, so the
original dispatch lost access to it. This means that the tooltip state
stopped updating. Which means yes it fixed the infinite loop but it also
broke initializing the tooltip position. I fixed it so that it's still
memoized but updating properly.

## Related Issue

#6613

## 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

* **Refactor**
* Restructured tooltip configuration logic across chart components
(Area, Bar, Funnel, Line, Scatter, Sankey, Sunburst, Treemap, Pie,
Radar, and RadialBar).
  * Updated internal tooltip state handling for improved consistency.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

useActiveTooltipDataPoints causes infinite re-render: Maximum update depth exceeded

2 participants