diff --git a/packages/react-dom/src/events/EnterLeaveEventPlugin.js b/packages/react-dom/src/events/EnterLeaveEventPlugin.js index f8c8d279db00..300772410c60 100644 --- a/packages/react-dom/src/events/EnterLeaveEventPlugin.js +++ b/packages/react-dom/src/events/EnterLeaveEventPlugin.js @@ -42,6 +42,12 @@ const eventTypes = { }, }; +// We track the lastNativeEvent to ensure that when we encounter +// cases where we process the same nativeEvent multiple times, +// which can happen when have multiple ancestors, that we don't +// duplicate enter +let lastNativeEvent; + const EnterLeaveEventPlugin = { eventTypes: eventTypes, @@ -163,9 +169,11 @@ const EnterLeaveEventPlugin = { accumulateEnterLeaveDispatches(leave, enter, from, to); - if (isOutEvent && from && nativeEventTarget !== fromNode) { + if (nativeEvent === lastNativeEvent) { + lastNativeEvent = null; return [leave]; } + lastNativeEvent = nativeEvent; return [leave, enter]; }, diff --git a/packages/react-dom/src/events/__tests__/EnterLeaveEventPlugin-test.js b/packages/react-dom/src/events/__tests__/EnterLeaveEventPlugin-test.js index b8bda1c67fe9..e91eb737914c 100644 --- a/packages/react-dom/src/events/__tests__/EnterLeaveEventPlugin-test.js +++ b/packages/react-dom/src/events/__tests__/EnterLeaveEventPlugin-test.js @@ -185,4 +185,55 @@ describe('EnterLeaveEventPlugin', () => { ReactDOM.render(, container); }); + + it('should call mouseEnter when pressing a non tracked React node', done => { + const mockFn = jest.fn(); + + class Parent extends React.Component { + constructor(props) { + super(props); + this.parentEl = React.createRef(); + } + + componentDidMount() { + ReactDOM.render(, this.parentEl.current); + } + + render() { + return
; + } + } + + class MouseEnterDetect extends React.Component { + constructor(props) { + super(props); + this.divRef = React.createRef(); + this.siblingEl = React.createRef(); + } + + componentDidMount() { + const attachedNode = document.createElement('div'); + this.divRef.current.appendChild(attachedNode); + attachedNode.dispatchEvent( + new MouseEvent('mouseout', { + bubbles: true, + cancelable: true, + relatedTarget: this.siblingEl.current, + }), + ); + expect(mockFn.mock.calls.length).toBe(1); + done(); + } + + render() { + return ( +
+
+
+ ); + } + } + + ReactDOM.render(, container); + }); });