Skip to content

Fixing event handler types#6970

Merged
ckifer merged 3 commits into
mainfrom
event-types
Feb 10, 2026
Merged

Fixing event handler types#6970
ckifer merged 3 commits into
mainfrom
event-types

Conversation

@PavelVanecek

@PavelVanecek PavelVanecek commented Feb 7, 2026

Copy link
Copy Markdown
Collaborator

Description

Added tests to make sure that the new function we added in #6942 is internally compatible with our type handlers.

Related Issue

#6645

Fixes #3362

Fixes #4872

Closes #5495

Motivation and Context

I suspect that the new functionality and hooks will draw more attention to the event handlers so I would like this to be easy to use.

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

Release Notes

  • New Features

    • Exported new public types: SankeyElementType, RadarPoint, InternalRadarProps, and ScatterPointItem for improved TypeScript support and better type inference in event handlers.
  • Bug Fixes

    • Made TreemapNode.root optional to prevent structure validation errors.
  • Refactor

    • Refined event handler type signatures across charts for more specific SVG element typing and better IDE support.
    • Removed onClick handler from Dot component.

@coderabbitai

coderabbitai Bot commented Feb 7, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

This PR systematically refines TypeScript type signatures across Recharts chart components, replacing generic types (any, MouseEvent) with specific ones for event handlers and data items, exports previously internal types, and adds comprehensive type-checking tests to validate event handler compatibility with utilities like getRelativeCoordinate.

Changes

Cohort / File(s) Summary
Event Handler Type Tightening
src/cartesian/Funnel.tsx, src/cartesian/ReferenceDot.tsx, src/cartesian/Scatter.tsx, src/polar/RadialBar.tsx, src/component/DefaultLegendContent.tsx, src/chart/Sankey.tsx, src/chart/Treemap.tsx, src/polar/Radar.tsx
Narrowed generic event handler types from any/MouseEvent/SVGElement to specific types like FunnelTrapezoidItem, DotProps, ScatterPointItem, SVGPathElement, SVGGraphicsElement, and SVGCircleElement across onClick, onMouseEnter/Leave/Move/Over/Out/Down/Up, and touch event signatures.
Radar Component Refactoring
src/polar/Radar.tsx
Exported RadarPoint and InternalRadarProps as public types; removed points prop from RadarProps; updated internal component signatures to use InternalRadarProps; changed SVG element type from SVGElement to SVGGraphicsElement; made name field in RadarPoint accept string | number.
Public Type Exports
src/index.ts, omnidoc/verifyExamples.spec.ts
Added exports for previously internal types: RadarPoint, InternalRadarProps, ScatterPointItem, and SankeyElementType; updated example verification list to reflect new public items.
Sankey Type Updates
src/chart/Sankey.tsx
Exported SankeyElementType type; updated all event handlers to use MouseEvent<SVGGraphicsElement>; refined Props type to use Omit<SVGProps<SVGSVGElement>, keyof SankeyProps> to avoid prop collisions.
Type Verification Tests
test/cartesian/Area.typed.spec.tsx, test/cartesian/Bar/Bar.typed.spec.tsx, test/cartesian/Funnel.typed.spec.tsx, test/cartesian/Line.typed.spec.tsx, test/cartesian/ReferenceArea.typed.spec.tsx, test/cartesian/ReferenceDot.typed.spec.tsx, test/cartesian/Scatter.typed.spec.tsx, test/chart/Sankey.typed.spec.tsx, test/chart/Treemap.typed.spec.tsx, test/component/Label.typed.spec.tsx, test/component/Legend.typed.spec.tsx, test/component/Text.typed.spec.tsx, test/polar/Radar.typed.spec.tsx
Added new TypeScript-focused test files verifying that getRelativeCoordinate accepts event types provided by Recharts event handlers across multiple components and interaction types (mouse, touch).
Event Handler Behavior Tests
test/cartesian/ReferenceArea.spec.tsx, test/cartesian/ReferenceDot.spec.tsx, test/cartesian/ReferenceLine/ReferenceLine.spec.tsx, test/chart/Sankey.spec.tsx, test/chart/Treemap.spec.tsx, test/polar/Radar.spec.tsx
Expanded event handling test coverage with comprehensive mouse and touch event assertions; refactored test expectations to use typed prop objects (SankeyLinkProps, SankeyNodeProps, TreemapNode, InternalRadarProps, DotProps).
Dot Component Cleanup
src/shape/Dot.tsx
Removed onClick prop from DotProps and its corresponding RechartsMouseEventHandler import; other event handlers remain unchanged.
Structural Improvements
src/cartesian/ReferenceArea.tsx, src/chart/Treemap.tsx, www/src/docs/exampleComponents/TreeMap/CustomContentTreemap.tsx, storybook/stories/API/chart/Treemap.stories.tsx, test/state/selectors/radarSelectors.spec.tsx
Made TreemapNode.root optional; applied optional chaining for safer root access in Treemap renderers; removed redundant TypeScript error suppression comments; fixed minor whitespace issues.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • PR #6852 — Adds/exports missing types in src/index.ts alongside similar public type surface expansion.
  • PR #6939 — Makes parallel event handler type tightening and removes ts-expect-error annotations in overlapping components.
  • PR #6682 — Updates Sankey-related public type exports and src/chart/Sankey.tsx typing changes in similar manner.

Suggested labels

typescript

Suggested reviewers

  • ckifer
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ 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%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fixing event handler types' directly describes the primary change: refining event handler type signatures across multiple components for better type safety.
Description check ✅ Passed The PR description covers key sections: it describes the changes (tests for type handler compatibility), references related issues (#6645, #3362, #4872, #5495), explains motivation (improving ease of use), and marks the appropriate change type and checklist items.
Linked Issues check ✅ Passed The PR fully addresses the three linked issues: #3362 (MouseEvent types on Legend and components now use React.MouseEvent with specific element types), #4872 (touch events now have proper type bindings via onTouchStart/Move/End handlers), and #5495 (mouse events are now strongly typed with specific data types instead of any).
Out of Scope Changes check ✅ Passed All changes are in scope: type refinements to event handlers, test files to validate compatibility, and optional chaining fixes in Treemap/CustomContentTreemap directly support the stated objective of ensuring the new getRelativeCoordinate function works with existing type handlers.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 event-types

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/cartesian/ReferenceDot.tsx`:
- Line 128: The Props type currently intersects Omit<DotProps, 'cx' | 'cy' |
'clipDot' | 'dangerouslySetInnerHTML'> with ReferenceDotProps, causing
conflicting mouse event handler signatures; update the Omit on DotProps in the
Props declaration to also exclude the mouse event handler keys (onClick,
onMouseDown, onMouseUp, onMouseOver, onMouseOut, onMouseEnter, onMouseMove,
onMouseLeave) so ReferenceDotProps' handler types are used (adjust the Omit list
in the Props type near the ReferenceDot.tsx export).

In `@test/cartesian/ReferenceDot.spec.tsx`:
- Around line 349-440: The tests in the 'events' describe use userEventSetup()
(which calls vi.advanceTimersByTime) and render LineChart that depends on Redux
autoBatchEnhancer/requestAnimationFrame, so enable fake timers for deterministic
behavior: add vi.useFakeTimers() in a beforeEach for the describe('events')
block (and restore real timers in an afterEach with vi.useRealTimers()) so that
userEventSetup, vi.advanceTimersByTime, and any RAF/timer-based batching behave
predictably.
🧹 Nitpick comments (10)
test/cartesian/ReferenceLine/ReferenceLine.spec.tsx (3)

688-718: Inconsistent use of expectLastCalledWith helper.

Line 690 correctly uses expectLastCalledWith(onClick, ...), but lines 694, 696, 700, 702, 706, 710, 714, and 718 fall back to expect(spy).toHaveBeenLastCalledWith(...). The coding guidelines say to prefer expectLastCalledWith for better typing and autocompletion.

♻️ Suggested fix (representative diff for a few lines)
-      expect(onMouseEnter).toHaveBeenLastCalledWith(expect.objectContaining({ type: 'mouseenter' }));
+      expectLastCalledWith(onMouseEnter, expect.objectContaining({ type: 'mouseenter' }));
       expect(onMouseOver).toHaveBeenCalledTimes(1);
-      expect(onMouseOver).toHaveBeenLastCalledWith(expect.objectContaining({ type: 'mouseover' }));
+      expectLastCalledWith(onMouseOver, expect.objectContaining({ type: 'mouseover' }));

Apply the same pattern to onMouseLeave, onMouseOut, onMouseMove, onTouchStart, onTouchMove, and onTouchEnd.

As per coding guidelines, test/**/*.{test,spec}.{ts,tsx}: "Use the expectLastCalledWith helper function instead of expect(spy).toHaveBeenLastCalledWith(...) for better typing and autocompletion".


685-686: Misleading variable name dot for a reference line element.

The selector targets .recharts-reference-line .recharts-reference-line-line, which is a line element, not a dot. Consider renaming to line or refLine for clarity.

-      const dot = container.querySelector('.recharts-reference-line .recharts-reference-line-line');
-      assertNotNull(dot);
+      const refLine = container.querySelector('.recharts-reference-line .recharts-reference-line-line');
+      assertNotNull(refLine);

Then update all subsequent references accordingly.


669-671: Stray r={3} prop on ReferenceLine.

r is a radius prop that belongs to ReferenceDot, not ReferenceLine. This appears to be a copy-paste artifact. While it's harmless (it'll be ignored or spread as an SVG attribute), it's misleading in a test that's meant to validate ReferenceLine event handlers.

           <ReferenceLine
             x={1}
-            r={3}
             onClick={onClick}
omnidoc/verifyExamples.spec.ts (1)

97-101: Naming concern: InternalRadarProps exported as public API.

Exporting a type prefixed with Internal as part of the public API is confusing for consumers — the name implies it's an internal implementation detail. Consider renaming it to something like RadarEventHandlerProps or RadarComposedProps to better communicate its intended public use, or keep it internal and expose a differently named alias.

This is a broader concern rooted in src/index.ts / src/polar/Radar.tsx, not this file specifically.

test/polar/Radar.typed.spec.tsx (1)

5-47: Inconsistent handler signatures across Radar event props — is this intentional?

onMouseEnter and onMouseLeave receive (data: InternalRadarProps, event) while all other mouse/touch handlers receive only (event). If this asymmetry is intentional (matching the actual runtime behavior), it may be worth a brief comment in the test explaining why, similar to the Treemap test's onClick comment. If unintentional, the underlying Radar types may need alignment.

test/cartesian/ReferenceArea.spec.tsx (1)

561-638: Good event handler coverage for ReferenceArea.

The new events describe block comprehensively tests all mouse and touch event handlers. A few observations:

  1. Misleading variable name on Line 603: dot queries .recharts-reference-area-rect — consider naming it rect or referenceAreaRect for clarity.

  2. Inconsistent use of expectLastCalledWith: Line 608 correctly uses the expectLastCalledWith helper, but lines 612, 614, 618–620, and 624 fall back to expect(spy).toHaveBeenLastCalledWith(...). As per coding guidelines: "Use the expectLastCalledWith helper function instead of expect(spy).toHaveBeenLastCalledWith(...) for better typing and autocompletion."

  3. Missing vi.useFakeTimers(): The test uses userEventSetup() (which internally relies on vi.advanceTimersByTime) but doesn't set up fake timers. As per coding guidelines: "Use vi.useFakeTimers() in all tests due to Redux autoBatchEnhancer dependency on timers and requestAnimationFrame."

Suggested improvements
   describe('events', () => {
+    vi.useFakeTimers();
+
     it('should fire event handlers when provided', async () => {
       const userEvent = userEventSetup();
-      const dot = container.querySelector('.recharts-reference-area-rect');
-      assertNotNull(dot);
+      const rect = container.querySelector('.recharts-reference-area-rect');
+      assertNotNull(rect);
 
-      await userEvent.click(dot);
+      await userEvent.click(rect);
       expect(onClick).toHaveBeenCalledTimes(1);
       expectLastCalledWith(onClick, expect.objectContaining({ type: 'click' }));
 
-      await userEvent.hover(dot);
+      await userEvent.hover(rect);
       expect(onMouseEnter).toHaveBeenCalledTimes(1);
-      expect(onMouseEnter).toHaveBeenLastCalledWith(expect.objectContaining({ type: 'mouseenter' }));
+      expectLastCalledWith(onMouseEnter, expect.objectContaining({ type: 'mouseenter' }));
test/chart/Treemap.spec.tsx (1)

373-470: Consider extracting the shared root object to reduce duplication.

The expectedTreemapNode / expectedNode objects in the onMouseEnter (Line 373), onMouseLeave (Line 518), and onClick (Line 646) tests all contain the identical root structure (~80 lines). Extracting it to a shared constant at the describe level would cut ~160 lines of duplication and make future maintenance easier.

That said, the refactor from inline literals to typed TreemapNode constants is a clear improvement over the previous approach.

src/chart/Treemap.tsx (1)

461-465: Narrowing to SVGGraphicsElement is a good improvement for type safety.

The event target correctly reflects that Treemap's Layer renders SVG <g> elements (which extend SVGGraphicsElement).

Note the asymmetry: onMouseEnter and onMouseLeave pass both (node, event) while onClick only passes (node) — no event. This is pre-existing behavior (confirmed by the test on line 644 of Treemap.spec.tsx), but it may surprise consumers. Consider adding a JSDoc note on onClick explaining this, or filing a follow-up to include the event parameter for consistency.

src/index.ts (1)

73-73: Exporting InternalRadarProps as public API — is the naming intentional?

The prefix "Internal" in a publicly exported type may confuse consumers into thinking it's not part of the stable API. Since this type describes the payload shape passed to Radar event handlers (e.g., onMouseEnter), a name like RadarEventPayload or RadarHandlerProps might better communicate its purpose.

If the name is intentional and temporary, consider adding a JSDoc note on the type definition in src/polar/Radar.tsx clarifying its role and stability.

test/cartesian/ReferenceDot.spec.tsx (1)

414-438: Prefer expectLastCalledWith helper over expect(spy).toHaveBeenLastCalledWith(...).

Line 410 correctly uses expectLastCalledWith, but the subsequent assertions on lines 414, 416, 420, 422, 426, 430, 434, and 438 fall back to expect(spy).toHaveBeenLastCalledWith(...). For consistency and the better typing/autocompletion the helper provides, consider using expectLastCalledWith throughout.

Example fix for one assertion
-      expect(onMouseEnter).toHaveBeenLastCalledWith(expectedDotProps, expect.objectContaining({ type: 'mouseenter' }));
+      expectLastCalledWith(onMouseEnter, expectedDotProps, expect.objectContaining({ type: 'mouseenter' }));

As per coding guidelines: "Use the expectLastCalledWith helper function instead of expect(spy).toHaveBeenLastCalledWith(...) for better typing and autocompletion"

onMouseLeave?: (dotProps: DotProps, e: React.MouseEvent<SVGCircleElement>) => void;
}

export type Props = Omit<DotProps, 'cx' | 'cy' | 'clipDot' | 'dangerouslySetInnerHTML'> & ReferenceDotProps;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's find and examine the Dot.tsx file
fd 'Dot\.tsx' --type f | head -10

Repository: recharts/recharts

Length of output: 153


🏁 Script executed:

# Also find ReferenceDot.tsx to see the full context
fd 'ReferenceDot\.tsx' --type f | head -10

Repository: recharts/recharts

Length of output: 91


🏁 Script executed:

# Search for DotProps definition
rg 'type DotProps|interface DotProps' --type ts --type tsx -A 15

Repository: recharts/recharts

Length of output: 88


🏁 Script executed:

# Search for ReferenceDotProps definition
rg 'type ReferenceDotProps|interface ReferenceDotProps' --type ts --type tsx -A 15

Repository: recharts/recharts

Length of output: 88


🏁 Script executed:

# Search for DotProps definition without file type restrictions
rg 'type DotProps|interface DotProps' -A 20

Repository: recharts/recharts

Length of output: 832


🏁 Script executed:

# Search for ReferenceDotProps definition
rg 'type ReferenceDotProps|interface ReferenceDotProps' -A 20

Repository: recharts/recharts

Length of output: 1423


🏁 Script executed:

# Read the Dot.tsx file to see DotProps
cat src/shape/Dot.tsx | head -150

Repository: recharts/recharts

Length of output: 1370


🏁 Script executed:

# Read ReferenceDot.tsx to see the Props definition and context
cat src/cartesian/ReferenceDot.tsx | head -150

Repository: recharts/recharts

Length of output: 5611


🏁 Script executed:

# Find PresentationAttributesWithProps definition
rg 'type PresentationAttributesWithProps|interface PresentationAttributesWithProps' -A 10

Repository: recharts/recharts

Length of output: 712


🏁 Script executed:

# Also check the adaptEventHandlers function to understand how handlers are managed
rg 'export.*adaptEventHandlers|function adaptEventHandlers' -A 20

Repository: recharts/recharts

Length of output: 1040


🏁 Script executed:

# Check SVGProps or similar to understand the standard event handlers
rg 'SVGProps|svgPropertiesNoEvents' -B 5 -A 10

Repository: recharts/recharts

Length of output: 50374


🏁 Script executed:

# Find DOMAttributesWithProps definition
rg 'type DOMAttributesWithProps|interface DOMAttributesWithProps' -A 10

Repository: recharts/recharts

Length of output: 594


🏁 Script executed:

# Check the full types.ts file for event handler related types
rg 'DOMAttributesWithProps|DOMAttributesAdaptChildEvent' -B 2 -A 10

Repository: recharts/recharts

Length of output: 2345


🏁 Script executed:

# See how the Props type is used in ReferenceDot
cat src/cartesian/ReferenceDot.tsx | tail -200 | head -150

Repository: recharts/recharts

Length of output: 4701


🏁 Script executed:

# Check if there are any issues reported in tests or if this is actually causing problems
rg 'ReferenceDot' src/cartesian/__tests__/ -l | head -5

Repository: recharts/recharts

Length of output: 125


🏁 Script executed:

# Get the complete list of event handlers from DOMAttributesWithProps
rg 'export interface DOMAttributesWithProps' -A 200 | head -250

Repository: recharts/recharts

Length of output: 11465


🏁 Script executed:

# Check if TypeScript compilation has any errors related to ReferenceDot
rg 'Cannot assign' src/cartesian/ReferenceDot.tsx

Repository: recharts/recharts

Length of output: 43


🏁 Script executed:

# Look for any type assertion or ts-ignore comments around the Props definition
rg '@ts-expect-error|@ts-ignore' src/cartesian/ReferenceDot.tsx

Repository: recharts/recharts

Length of output: 113


Omit the conflicting mouse event handler keys from DotProps.

Props intersects Omit<DotProps, ...> with ReferenceDotProps, which redefines mouse event handlers (onClick, onMouseDown, onMouseUp, onMouseOver, onMouseOut, onMouseEnter, onMouseMove, onMouseLeave) with a different signature: (dotProps: DotProps, e: React.MouseEvent<SVGCircleElement>) => void. Since DotProps includes these handlers from DOMAttributesWithProps with the RechartsMouseEventHandler signature, the intersection creates conflicting overloads. Add these event handler keys to the Omit list to resolve the type conflict.

🤖 Prompt for AI Agents
In `@src/cartesian/ReferenceDot.tsx` at line 128, The Props type currently
intersects Omit<DotProps, 'cx' | 'cy' | 'clipDot' | 'dangerouslySetInnerHTML'>
with ReferenceDotProps, causing conflicting mouse event handler signatures;
update the Omit on DotProps in the Props declaration to also exclude the mouse
event handler keys (onClick, onMouseDown, onMouseUp, onMouseOver, onMouseOut,
onMouseEnter, onMouseMove, onMouseLeave) so ReferenceDotProps' handler types are
used (adjust the Omit list in the Props type near the ReferenceDot.tsx export).

Comment thread test/cartesian/ReferenceDot.spec.tsx
@codecov

codecov Bot commented Feb 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.38%. Comparing base (108be69) to head (50f18e8).
⚠️ Report is 13 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #6970   +/-   ##
=======================================
  Coverage   90.38%   90.38%           
=======================================
  Files         515      515           
  Lines       38497    38501    +4     
  Branches     5338     5338           
=======================================
+ Hits        34797    34801    +4     
  Misses       3691     3691           
  Partials        9        9           

☔ 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.

@github-actions

github-actions Bot commented Feb 7, 2026

Copy link
Copy Markdown
Contributor

Staging Deployment Details

These deployments will remain available for 30 days.

To update snapshots: Comment /update-snapshots on this PR to automatically update the baseline screenshots.

@codecov

codecov Bot commented Feb 7, 2026

Copy link
Copy Markdown

Bundle Report

Bundle size has no change ✅

@PavelVanecek
PavelVanecek requested a review from ckifer February 8, 2026 00:05

@ckifer ckifer left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nicee

@ckifer
ckifer merged commit 7b1b097 into main Feb 10, 2026
48 checks passed
@ckifer
ckifer deleted the event-types branch February 10, 2026 19:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants