From b34f042e5b6fa30c645b71d38a0b1ecf785af2cc Mon Sep 17 00:00:00 2001 From: Rango Yuan Date: Tue, 1 Oct 2019 20:03:14 +0800 Subject: [PATCH] Fix mouseenter handlers fired twice (#16928) --- .../components/fixtures/mouse-events/index.js | 2 + .../fixtures/mouse-events/mouse-enter.js | 73 +++++++++++++++++++ .../src/events/EnterLeaveEventPlugin.js | 4 + .../__tests__/EnterLeaveEventPlugin-test.js | 51 +++++++++++++ 4 files changed, 130 insertions(+) create mode 100644 fixtures/dom/src/components/fixtures/mouse-events/mouse-enter.js diff --git a/fixtures/dom/src/components/fixtures/mouse-events/index.js b/fixtures/dom/src/components/fixtures/mouse-events/index.js index 4c121bf09479..3624d4e837b2 100644 --- a/fixtures/dom/src/components/fixtures/mouse-events/index.js +++ b/fixtures/dom/src/components/fixtures/mouse-events/index.js @@ -1,5 +1,6 @@ import FixtureSet from '../../FixtureSet'; import MouseMovement from './mouse-movement'; +import MouseEnter from './mouse-enter'; const React = window.React; @@ -8,6 +9,7 @@ class MouseEvents extends React.Component { return ( + ); } diff --git a/fixtures/dom/src/components/fixtures/mouse-events/mouse-enter.js b/fixtures/dom/src/components/fixtures/mouse-events/mouse-enter.js new file mode 100644 index 000000000000..c0fbcbda6a43 --- /dev/null +++ b/fixtures/dom/src/components/fixtures/mouse-events/mouse-enter.js @@ -0,0 +1,73 @@ +import TestCase from '../../TestCase'; + +const React = window.React; +const ReactDOM = window.ReactDOM; + +const MouseEnter = () => { + const containerRef = React.useRef(); + + React.useEffect(function() { + const hostEl = containerRef.current; + ReactDOM.render(, hostEl, () => { + ReactDOM.render(, hostEl.childNodes[1]); + }); + }, []); + + return ( + + +
  • Mouse enter the boxes below, from different borders
  • +
    + + Mouse enter call count should equal to 1;
    + Issue{' '} + + #16763 + {' '} + should not happen. +
    +
    +
    + + ); +}; + +const MouseEnterDetect = () => { + const [log, setLog] = React.useState({}); + const firstEl = React.useRef(); + const siblingEl = React.useRef(); + + const onMouseEnter = e => { + const timeStamp = e.timeStamp; + setLog(log => { + const callCount = 1 + (log.timeStamp === timeStamp ? log.callCount : 0); + return { + timeStamp, + callCount, + }; + }); + }; + + return ( + +
    + Mouse enter call count: {log.callCount || ''} +
    +
    + + ); +}; + +export default MouseEnter; diff --git a/packages/react-dom/src/events/EnterLeaveEventPlugin.js b/packages/react-dom/src/events/EnterLeaveEventPlugin.js index 57ba9e765915..95d1aa0a7e6a 100644 --- a/packages/react-dom/src/events/EnterLeaveEventPlugin.js +++ b/packages/react-dom/src/events/EnterLeaveEventPlugin.js @@ -163,6 +163,10 @@ const EnterLeaveEventPlugin = { accumulateEnterLeaveDispatches(leave, enter, from, to); + if (isOutEvent && from && nativeEventTarget !== fromNode) { + return [leave]; + } + 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 33dd3e964ee4..b8bda1c67fe9 100644 --- a/packages/react-dom/src/events/__tests__/EnterLeaveEventPlugin-test.js +++ b/packages/react-dom/src/events/__tests__/EnterLeaveEventPlugin-test.js @@ -134,4 +134,55 @@ describe('EnterLeaveEventPlugin', () => { expect(childEnterCalls).toBe(1); expect(parentEnterCalls).toBe(0); }); + + // Test for https://github.com/facebook/react/issues/16763. + it('should call mouseEnter once from sibling rendered inside a rendered component', 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.firstEl = React.createRef(); + this.siblingEl = React.createRef(); + } + + componentDidMount() { + this.siblingEl.current.dispatchEvent( + new MouseEvent('mouseout', { + bubbles: true, + cancelable: true, + relatedTarget: this.firstEl.current, + }), + ); + expect(mockFn.mock.calls.length).toBe(1); + done(); + } + + render() { + return ( + +
    +
    + + ); + } + } + + ReactDOM.render(, container); + }); });