Conversation
WalkthroughRefactors event throttling across multiple middlewares to cache latest event payloads per event type and run a leading-edge callback, while updating omnidoc type-simplification to strip Changes
Sequence DiagramsequenceDiagram
actor EventSource as Event Source
participant Middleware
participant Cache as Latest Event Cache
participant Callback
participant State
EventSource->>Middleware: emit event (payload)
Middleware->>Cache: store/update latest payload
Middleware->>Callback: execute immediately (leading-edge)
Callback->>State: dispatch/update
Note over Middleware, Cache: cooldown started (timeout / RAF)
EventSource->>Middleware: emit event (during cooldown)
Middleware->>Cache: update latest payload
Note over Middleware: skip immediate execution (throttled)
Note over Middleware: cooldown expires
Middleware->>Cache: read latest payload
Middleware->>Callback: execute with latest payload (trailing)
Callback->>State: dispatch/update
Middleware->>Cache: clear cached payload
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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 |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/state/keyboardEventsMiddleware.ts (1)
44-125: Throttle cooldown can get stuck when the callback exits early.When the trailing timeout fires,
callback()can return before resettingtimeoutId/rafId(e.g., Enter key path, invalid key, or accessibility disabled). Because the timeout handler only clears IDs when no payload exists, this leavestimeoutIdnon‑null and blocks subsequent keydowns. Ensure cleanup runs on every exit.🛠️ Suggested fix: always clear IDs via try/finally
- const callback = () => { - const currentState = listenerApi.getState(); - const accessibilityLayerIsActive = currentState.rootProps.accessibilityLayer !== false; - if (!accessibilityLayerIsActive) { - return; - } - const { keyboardInteraction } = currentState.tooltip; - const key = latestKeyboardActionPayload; - if (key !== 'ArrowRight' && key !== 'ArrowLeft' && key !== 'Enter') { - return; - } + const callback = () => { + try { + const currentState = listenerApi.getState(); + const accessibilityLayerIsActive = currentState.rootProps.accessibilityLayer !== false; + if (!accessibilityLayerIsActive) { + return; + } + const { keyboardInteraction } = currentState.tooltip; + const key = latestKeyboardActionPayload; + if (key !== 'ArrowRight' && key !== 'ArrowLeft' && key !== 'Enter') { + return; + } ... - listenerApi.dispatch( - setKeyboardInteraction({ - active: true, - activeIndex: nextIndex.toString(), - activeCoordinate: coordinate, - }), - ); - rafId = null; - timeoutId = null; + listenerApi.dispatch( + setKeyboardInteraction({ + active: true, + activeIndex: nextIndex.toString(), + activeCoordinate: coordinate, + }), + ); + } finally { + rafId = null; + timeoutId = null; + } };test/state/throttling.spec.ts (1)
159-164: Replaceas anywithas unknown as SyntheticEventto match the codebase pattern.The
as anyviolates the coding guideline that forbidsanytypes. While the suggestedsatisfiesapproach is cleaner, the related test file (test/state/externalEventsMiddleware.spec.ts) usesas unknown as SyntheticEventfor the same mock pattern. This is the established pattern in the codebase for typing mock events that satisfy React'sSyntheticEventinterface.Apply this change to lines 159-164 and 180-184 (and any other occurrences in this file):
reactEvent: mockEvent as any, + reactEvent: mockEvent as unknown as SyntheticEvent,
🤖 Fix all issues with AI agents
In `@test/state/throttling.spec.ts`:
- Around line 88-93: The test "should execute first mousemove event immediately
(leading edge)" in throttling.spec.ts is empty — either delete this no-op test
or implement a concrete assertion; to implement, simulate a mousemove dispatch
and assert the leading-edge behavior by spying on the middleware/handler (e.g.,
use jest.spyOn(console, 'log') or spy on the externalEventsMiddleware handler)
or by asserting store state changes after the first dispatch; update the test to
call the same setup used by externalEventsMiddleware tests, dispatch the
mousemove event, and expect the spy to have been called exactly once immediately
(and optionally restore the spy).
- Around line 169-172: The test currently calls vi.runAllTimers() inside the act
callback to complete the timeout cleanup; replace that call with
vi.runOnlyPendingTimers() so only scheduled pending timers run and avoid
starting any new timers—update the act block that wraps vi.runAllTimers() to
call vi.runOnlyPendingTimers() instead (refer to the act(...) invocation and the
vi.runAllTimers() symbol in the throttling.spec.ts snippet).
| // Complete the timeout to clean up the Map | ||
| act(() => { | ||
| vi.advanceTimersByTime(100); | ||
| vi.runAllTimers(); | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n test/state/throttling.spec.ts | sed -n '160,180p'Repository: recharts/recharts
Length of output: 826
🏁 Script executed:
cat -n test/state/throttling.spec.ts | sed -n '140,175p'Repository: recharts/recharts
Length of output: 1213
🏁 Script executed:
cat -n test/state/throttling.spec.ts | sed -n '1,30p'Repository: recharts/recharts
Length of output: 1323
🏁 Script executed:
cat -n test/state/throttling.spec.ts | sed -n '26,40p'Repository: recharts/recharts
Length of output: 578
Replace vi.runAllTimers() with vi.runOnlyPendingTimers().
This test does not use the createSelectorTestCase helper, so the guideline to avoid vi.runAllTimers() applies here. Use vi.runOnlyPendingTimers() to prevent potential runaway timers.
Suggested change
- vi.runAllTimers();
+ vi.runOnlyPendingTimers();📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Complete the timeout to clean up the Map | |
| act(() => { | |
| vi.advanceTimersByTime(100); | |
| vi.runAllTimers(); | |
| }); | |
| // Complete the timeout to clean up the Map | |
| act(() => { | |
| vi.runOnlyPendingTimers(); | |
| }); |
🤖 Prompt for AI Agents
In `@test/state/throttling.spec.ts` around lines 169 - 172, The test currently
calls vi.runAllTimers() inside the act callback to complete the timeout cleanup;
replace that call with vi.runOnlyPendingTimers() so only scheduled pending
timers run and avoid starting any new timers—update the act block that wraps
vi.runAllTimers() to call vi.runOnlyPendingTimers() instead (refer to the
act(...) invocation and the vi.runAllTimers() symbol in the throttling.spec.ts
snippet).
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #6938 +/- ##
==========================================
- Coverage 94.30% 94.28% -0.03%
==========================================
Files 573 573
Lines 56891 56938 +47
Branches 5254 5265 +11
==========================================
+ Hits 53653 53683 +30
- Misses 3229 3246 +17
Partials 9 9 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
Staging Deployment Details
These deployments will remain available for 30 days. To update snapshots: Comment |
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
test/state/throttling.spec.ts (2)
142-147: Mock handler is not reset between tests.The
mockHandleris defined once at the describe block level but never reset between tests. This causestoHaveBeenCalledTimesassertions to accumulate across tests, which could lead to flaky or incorrect test results.🐛 Proposed fix
describe('externalEventsMiddleware', () => { const mockHandler = vi.fn(); const mockEvent = { type: 'click', persist: vi.fn(), }; + beforeEach(() => { + mockHandler.mockClear(); + }); + it('should execute first event immediately (leading edge) when throttleDelay is a number', () => {
45-47: Also replacevi.runAllTimers()here.For consistency with the coding guidelines, this instance should also use
vi.runOnlyPendingTimers().♻️ Proposed fix
act(() => { - vi.runAllTimers(); + vi.runOnlyPendingTimers(); });
🧹 Nitpick comments (2)
src/state/keyboardEventsMiddleware.ts (1)
102-105: Thefinallyblock may cleartimeoutIdprematurely in the trailing-edge path.When
callback()is invoked from within thesetTimeouthandler (line 122), thefinallyblock setstimeoutId = nullbefore the outer code in lines 123-126 can check it or set it again. This appears intentional since the trailing callback should complete the throttle cycle, but the logic in lines 121-126 checkslatestKeyboardActionPayloadaftercallback()has already run.Looking more closely:
callback()at line 122 setstimeoutId = nullin finally, then the code at lines 123-126 either does nothing (no new payload) or setstimeoutId = nullagain. This is redundant but not harmful.♻️ Optional: Simplify the trailing-edge cleanup
The
elsebranch at lines 123-126 is redundant sincetimeoutIdandrafIdare already cleared in thefinallyblock ofcallback(). Consider simplifying:timeoutId = setTimeout(() => { if (latestKeyboardActionPayload) { callback(); - } else { - timeoutId = null; - rafId = null; } }, throttleDelay);test/state/throttling.spec.ts (1)
118-139: Keyboard throttling test covers the basic case well.The test validates that keyboard events follow the same throttle pattern as mouse events. Consider adding a test to verify the leading-edge execution (handler called immediately on first event) similar to the
externalEventsMiddlewaretest at line 149.♻️ Optional: Add leading-edge verification for keyboard events
To fully test the throttle behavior, consider adding assertions that verify the first event executes immediately:
it('should execute first keydown event immediately (leading edge) when throttleDelay is a number', () => { // Setup store with rootProps.accessibilityLayer and tooltip state // to enable the keyboard handler to actually dispatch store.dispatch(setEventSettings({ throttleDelay: 100, throttledEvents: 'all' })); store.dispatch(keyDownAction('ArrowRight')); // Verify leading-edge execution occurred (check state changes) expect(vi.getTimerCount()).toBe(1); // Trailing timer scheduled });
|
Staging Deployment Details
These deployments will remain available for 30 days. To update snapshots: Comment |
Description
Followup to the robot review from #6924 which turned out to be correct and useful
ReadonlyArray<X>asArray<readonly X>which was wrong so I removed the readonly completely - it's irrelevant on the website anywayRelated Issue
#6924
Types of changes
Checklist:
Summary by CodeRabbit
Improvements
Documentation
readonlyqualifiers across API docs and Storybook arg types for array/coordinate props.Tests
✏️ Tip: You can customize this high-level summary in your review settings.