Skip to content

Commit b34f042

Browse files
yuanoooktrueadm
authored andcommitted
Fix mouseenter handlers fired twice (#16928)
1 parent 05dc814 commit b34f042

File tree

4 files changed

+130
-0
lines changed

4 files changed

+130
-0
lines changed

fixtures/dom/src/components/fixtures/mouse-events/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import FixtureSet from '../../FixtureSet';
22
import MouseMovement from './mouse-movement';
3+
import MouseEnter from './mouse-enter';
34

45
const React = window.React;
56

@@ -8,6 +9,7 @@ class MouseEvents extends React.Component {
89
return (
910
<FixtureSet title="Mouse Events">
1011
<MouseMovement />
12+
<MouseEnter />
1113
</FixtureSet>
1214
);
1315
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import TestCase from '../../TestCase';
2+
3+
const React = window.React;
4+
const ReactDOM = window.ReactDOM;
5+
6+
const MouseEnter = () => {
7+
const containerRef = React.useRef();
8+
9+
React.useEffect(function() {
10+
const hostEl = containerRef.current;
11+
ReactDOM.render(<MouseEnterDetect />, hostEl, () => {
12+
ReactDOM.render(<MouseEnterDetect />, hostEl.childNodes[1]);
13+
});
14+
}, []);
15+
16+
return (
17+
<TestCase
18+
title="Mouse Enter"
19+
description=""
20+
affectedBrowsers="Chrome, Safari, Firefox">
21+
<TestCase.Steps>
22+
<li>Mouse enter the boxes below, from different borders</li>
23+
</TestCase.Steps>
24+
<TestCase.ExpectedResult>
25+
Mouse enter call count should equal to 1; <br />
26+
Issue{' '}
27+
<a
28+
rel="noopener noreferrer"
29+
target="_blank"
30+
href="https://github.com/facebook/react/issues/16763">
31+
#16763
32+
</a>{' '}
33+
should not happen.
34+
<br />
35+
</TestCase.ExpectedResult>
36+
<div ref={containerRef} />
37+
</TestCase>
38+
);
39+
};
40+
41+
const MouseEnterDetect = () => {
42+
const [log, setLog] = React.useState({});
43+
const firstEl = React.useRef();
44+
const siblingEl = React.useRef();
45+
46+
const onMouseEnter = e => {
47+
const timeStamp = e.timeStamp;
48+
setLog(log => {
49+
const callCount = 1 + (log.timeStamp === timeStamp ? log.callCount : 0);
50+
return {
51+
timeStamp,
52+
callCount,
53+
};
54+
});
55+
};
56+
57+
return (
58+
<React.Fragment>
59+
<div
60+
ref={firstEl}
61+
onMouseEnter={onMouseEnter}
62+
style={{
63+
border: '1px solid #d9d9d9',
64+
padding: '20px 20px',
65+
}}>
66+
Mouse enter call count: {log.callCount || ''}
67+
</div>
68+
<div ref={siblingEl} />
69+
</React.Fragment>
70+
);
71+
};
72+
73+
export default MouseEnter;

packages/react-dom/src/events/EnterLeaveEventPlugin.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,10 @@ const EnterLeaveEventPlugin = {
163163

164164
accumulateEnterLeaveDispatches(leave, enter, from, to);
165165

166+
if (isOutEvent && from && nativeEventTarget !== fromNode) {
167+
return [leave];
168+
}
169+
166170
return [leave, enter];
167171
},
168172
};

packages/react-dom/src/events/__tests__/EnterLeaveEventPlugin-test.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,4 +134,55 @@ describe('EnterLeaveEventPlugin', () => {
134134
expect(childEnterCalls).toBe(1);
135135
expect(parentEnterCalls).toBe(0);
136136
});
137+
138+
// Test for https://github.com/facebook/react/issues/16763.
139+
it('should call mouseEnter once from sibling rendered inside a rendered component', done => {
140+
const mockFn = jest.fn();
141+
142+
class Parent extends React.Component {
143+
constructor(props) {
144+
super(props);
145+
this.parentEl = React.createRef();
146+
}
147+
148+
componentDidMount() {
149+
ReactDOM.render(<MouseEnterDetect />, this.parentEl.current);
150+
}
151+
152+
render() {
153+
return <div ref={this.parentEl} />;
154+
}
155+
}
156+
157+
class MouseEnterDetect extends React.Component {
158+
constructor(props) {
159+
super(props);
160+
this.firstEl = React.createRef();
161+
this.siblingEl = React.createRef();
162+
}
163+
164+
componentDidMount() {
165+
this.siblingEl.current.dispatchEvent(
166+
new MouseEvent('mouseout', {
167+
bubbles: true,
168+
cancelable: true,
169+
relatedTarget: this.firstEl.current,
170+
}),
171+
);
172+
expect(mockFn.mock.calls.length).toBe(1);
173+
done();
174+
}
175+
176+
render() {
177+
return (
178+
<React.Fragment>
179+
<div ref={this.firstEl} onMouseEnter={mockFn} />
180+
<div ref={this.siblingEl} />
181+
</React.Fragment>
182+
);
183+
}
184+
}
185+
186+
ReactDOM.render(<Parent />, container);
187+
});
137188
});

0 commit comments

Comments
 (0)