Skip to content

Commit 80596c2

Browse files
authored
Fix incorrect calculation of onMouseEnter/Leave component path (#11164)
* Add a regression test for #10906 * Turn while conditions into breaks without changing the logic This will be easier to follow when we add more code there. * var => const/let So that I can add a block scoped variable. * Check alternates when comparing to the common ancestor This is the actual bugfix.
1 parent 08cbc25 commit 80596c2

2 files changed

Lines changed: 62 additions & 6 deletions

File tree

src/renderers/dom/shared/eventPlugins/__tests__/EnterLeaveEventPlugin-test.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ var EnterLeaveEventPlugin;
1313
var React;
1414
var ReactDOM;
1515
var ReactDOMComponentTree;
16+
var ReactTestUtils;
1617

1718
describe('EnterLeaveEventPlugin', () => {
1819
beforeEach(() => {
1920
jest.resetModules();
2021

2122
React = require('react');
2223
ReactDOM = require('react-dom');
24+
ReactTestUtils = require('react-dom/test-utils');
2325
// TODO: can we express this test with only public API?
2426
ReactDOMComponentTree = require('ReactDOMComponentTree');
2527
EnterLeaveEventPlugin = require('EnterLeaveEventPlugin');
@@ -58,4 +60,38 @@ describe('EnterLeaveEventPlugin', () => {
5860
expect(enter.target).toBe(div);
5961
expect(enter.relatedTarget).toBe(iframe.contentWindow);
6062
});
63+
64+
// Regression test for https://github.com/facebook/react/issues/10906.
65+
it('should find the common parent after updates', () => {
66+
let parentEnterCalls = 0;
67+
let childEnterCalls = 0;
68+
let parent = null;
69+
class Parent extends React.Component {
70+
render() {
71+
return (
72+
<div
73+
onMouseEnter={() => parentEnterCalls++}
74+
ref={node => (parent = node)}>
75+
{this.props.showChild &&
76+
<div onMouseEnter={() => childEnterCalls++} />}
77+
</div>
78+
);
79+
}
80+
}
81+
82+
const div = document.createElement('div');
83+
ReactDOM.render(<Parent />, div);
84+
// The issue only reproduced on insertion during the first update.
85+
ReactDOM.render(<Parent showChild={true} />, div);
86+
87+
// Enter from parent into the child.
88+
ReactTestUtils.simulateNativeEventOnNode('topMouseOut', parent, {
89+
target: parent,
90+
relatedTarget: parent.firstChild,
91+
});
92+
93+
// Entering a child should fire on the child, not on the parent.
94+
expect(childEnterCalls).toBe(1);
95+
expect(parentEnterCalls).toBe(0);
96+
});
6197
});

src/renderers/shared/shared/ReactTreeTraversal.js

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,18 +110,38 @@ function traverseTwoPhase(inst, fn, arg) {
110110
* "entered" or "left" that element.
111111
*/
112112
function traverseEnterLeave(from, to, fn, argFrom, argTo) {
113-
var common = from && to ? getLowestCommonAncestor(from, to) : null;
114-
var pathFrom = [];
115-
while (from && from !== common) {
113+
const common = from && to ? getLowestCommonAncestor(from, to) : null;
114+
const pathFrom = [];
115+
while (true) {
116+
if (!from) {
117+
break;
118+
}
119+
if (from === common) {
120+
break;
121+
}
122+
const alternate = from.alternate;
123+
if (alternate !== null && alternate === common) {
124+
break;
125+
}
116126
pathFrom.push(from);
117127
from = getParent(from);
118128
}
119-
var pathTo = [];
120-
while (to && to !== common) {
129+
const pathTo = [];
130+
while (true) {
131+
if (!to) {
132+
break;
133+
}
134+
if (to === common) {
135+
break;
136+
}
137+
const alternate = to.alternate;
138+
if (alternate !== null && alternate === common) {
139+
break;
140+
}
121141
pathTo.push(to);
122142
to = getParent(to);
123143
}
124-
var i;
144+
let i;
125145
for (i = 0; i < pathFrom.length; i++) {
126146
fn(pathFrom[i], 'bubbled', argFrom);
127147
}

0 commit comments

Comments
 (0)