diff --git a/fixtures/dom/src/components/fixtures/mouse-events/index.js b/fixtures/dom/src/components/fixtures/mouse-events/index.js index 4c121bf094790..3624d4e837b2f 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 0000000000000..c0fbcbda6a432 --- /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 57ba9e7659153..95d1aa0a7e6af 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 33dd3e964ee40..b8bda1c67fe9f 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); + }); });