Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix mouseenter handlers fired twice #16928

Merged
merged 2 commits into from Oct 1, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions 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;

Expand All @@ -8,6 +9,7 @@ class MouseEvents extends React.Component {
return (
<FixtureSet title="Mouse Events">
<MouseMovement />
<MouseEnter />
</FixtureSet>
);
}
Expand Down
73 changes: 73 additions & 0 deletions 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(<MouseEnterDetect />, hostEl, () => {
ReactDOM.render(<MouseEnterDetect />, hostEl.childNodes[1]);
});
}, []);

return (
<TestCase
title="Mouse Enter"
description=""
affectedBrowsers="Chrome, Safari, Firefox">
<TestCase.Steps>
<li>Mouse enter the boxes below, from different borders</li>
</TestCase.Steps>
<TestCase.ExpectedResult>
Mouse enter call count should equal to 1; <br />
Issue{' '}
<a
rel="noopener noreferrer"
target="_blank"
href="https://github.com/facebook/react/issues/16763">
#16763
</a>{' '}
should not happen.
<br />
</TestCase.ExpectedResult>
<div ref={containerRef} />
</TestCase>
);
};

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 (
<React.Fragment>
<div
ref={firstEl}
onMouseEnter={onMouseEnter}
style={{
border: '1px solid #d9d9d9',
padding: '20px 20px',
}}>
Mouse enter call count: {log.callCount || ''}
</div>
<div ref={siblingEl} />
</React.Fragment>
);
};

export default MouseEnter;
4 changes: 4 additions & 0 deletions packages/react-dom/src/events/EnterLeaveEventPlugin.js
Expand Up @@ -146,6 +146,10 @@ const EnterLeaveEventPlugin = {

accumulateEnterLeaveDispatches(leave, enter, from, to);

if (isOutEvent && from && nativeEventTarget !== fromNode) {
return [leave];
}

return [leave, enter];
},
};
Expand Down
Expand Up @@ -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(<MouseEnterDetect />, this.parentEl.current);
}

render() {
return <div ref={this.parentEl} />;
}
}

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 (
<React.Fragment>
<div ref={this.firstEl} onMouseEnter={mockFn} />
<div ref={this.siblingEl} />
</React.Fragment>
);
}
}

ReactDOM.render(<Parent />, container);
});
});