Fix mouseenter handlers fired twice (#16928)
Rango Yuan committed
Oct 1, 2019 at 20:03 UTC
b34f042e5b6fa30c645b71d38a0b1ecf785af2cc
4 files changed
+130
fixtures/dom/src/components/fixtures/mouse-events/index.js
+2
@@ -1,5 +1,6 @@
1
import FixtureSet from '../../FixtureSet';
2
import MouseMovement from './mouse-movement';
3
+import MouseEnter from './mouse-enter';
4
5
const React = window.React;
6
@@ -8,6 +9,7 @@ class MouseEvents extends React.Component {
9
return (
10
<FixtureSet title="Mouse Events">
11
<MouseMovement />
12
+ <MouseEnter />
13
</FixtureSet>
14
);
15
}
fixtures/dom/src/components/fixtures/mouse-events/mouse-enter.js
new
+73
@@ -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
+4
@@ -163,6 +163,10 @@ const EnterLeaveEventPlugin = {
163
164
accumulateEnterLeaveDispatches(leave, enter, from, to);
165
166
+ if (isOutEvent && from && nativeEventTarget !== fromNode) {
167
+ return [leave];
168
+ }
169
+
170
return [leave, enter];
171
},
172
};
packages/react-dom/src/events/__tests__/EnterLeaveEventPlugin-test.js
+51
@@ -134,4 +134,55 @@ describe('EnterLeaveEventPlugin', () => {
134
expect(childEnterCalls).toBe(1);
135
expect(parentEnterCalls).toBe(0);
136
});
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
+ });
188
});