[react-events] Fix Scope listener issue (#16658)
Dominic Gannaway committed
Sep 4, 2019 at 18:57 UTC
9ff60ff16b289a9efce46e21fe223aec1638b5d6
4 files changed
+87
-25
packages/react-reconciler/src/ReactFiberCommitWork.js
+2
-2
@@ -1343,7 +1343,7 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
1343
const prevListeners = oldProps.listeners;
1344
const nextListeners = newProps.listeners;
1345
if (prevListeners !== nextListeners) {
1346
- updateEventListeners(nextListeners, finishedWork);
1346
+ updateEventListeners(nextListeners, finishedWork, null);
1347
}
1348
}
1349
}
@@ -1400,7 +1400,7 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
1400
const prevListeners = oldProps.listeners;
1401
const nextListeners = newProps.listeners;
1402
if (prevListeners !== nextListeners) {
1403
- updateEventListeners(nextListeners, finishedWork);
1403
+ updateEventListeners(nextListeners, finishedWork, null);
1404
}
1405
}
1406
}
packages/react-reconciler/src/ReactFiberCompleteWork.js
+16
-3
@@ -724,7 +724,11 @@ function completeWork(
724
if (enableFlareAPI) {
725
const listeners = newProps.listeners;
726
if (listeners != null) {
727
- updateEventListeners(listeners, workInProgress);
727
+ updateEventListeners(
728
+ listeners,
729
+ workInProgress,
730
+ rootContainerInstance,
731
+ );
732
}
733
}
734
} else {
@@ -744,7 +748,11 @@ function completeWork(
748
if (enableFlareAPI) {
749
const listeners = newProps.listeners;
750
if (listeners != null) {
747
- updateEventListeners(listeners, workInProgress);
751
+ updateEventListeners(
752
+ listeners,
753
+ workInProgress,
754
+ rootContainerInstance,
755
+ );
756
}
757
}
758
@@ -1233,7 +1241,12 @@ function completeWork(
1241
if (enableFlareAPI) {
1242
const listeners = newProps.listeners;
1243
if (listeners != null) {
1236
- updateEventListeners(listeners, workInProgress);
1244
+ const rootContainerInstance = getRootHostContainer();
1245
+ updateEventListeners(
1246
+ listeners,
1247
+ workInProgress,
1248
+ rootContainerInstance,
1249
+ );
1250
}
1251
}
1252
if (workInProgress.ref !== null) {
packages/react-reconciler/src/ReactFiberEvents.js
+44
-17
@@ -8,7 +8,7 @@
8
*/
9
10
import type {Fiber} from './ReactFiber';
11
-import type {Instance} from './ReactFiberHostConfig';
11
+import type {Container, Instance} from './ReactFiberHostConfig';
12
import type {
13
ReactEventResponder,
14
ReactEventResponderInstance,
@@ -53,6 +53,7 @@ function mountEventResponder(
53
ReactEventResponder<any, any>,
54
ReactEventResponderInstance<any, any>,
55
>,
56
+ rootContainerInstance: null | Container,
57
) {
58
let responderState = emptyObject;
59
const getInitialState = responder.getInitialState;
@@ -65,25 +66,28 @@ function mountEventResponder(
66
responderState,
67
fiber,
68
);
68
- let instance = null;
69
- let node = fiber;
70
- while (node !== null) {
71
- const tag = node.tag;
72
- if (tag === HostComponent) {
73
- instance = node.stateNode;
74
- break;
75
- } else if (tag === HostRoot) {
76
- instance = node.stateNode.containerInfo;
77
- break;
69
+
70
+ if (!rootContainerInstance) {
71
+ let node = fiber;
72
+ while (node !== null) {
73
+ const tag = node.tag;
74
+ if (tag === HostComponent) {
75
+ rootContainerInstance = node.stateNode;
76
+ break;
77
+ } else if (tag === HostRoot) {
78
+ rootContainerInstance = node.stateNode.containerInfo;
79
+ break;
80
+ }
81
+ node = node.return;
82
}
79
- node = node.return;
83
}
84
+
85
mountResponderInstance(
86
responder,
87
responderInstance,
88
responderProps,
89
responderState,
86
- ((instance: any): Instance),
90
+ ((rootContainerInstance: any): Instance),
91
);
92
respondersMap.set(responder, responderInstance);
93
}
@@ -96,6 +100,7 @@ function updateEventListener(
100
ReactEventResponder<any, any>,
101
ReactEventResponderInstance<any, any>,
102
>,
103
+ rootContainerInstance: null | Container,
104
): void {
105
let responder;
106
let props;
@@ -127,7 +132,13 @@ function updateEventListener(
132
133
if (responderInstance === undefined) {
134
// Mount (happens in either complete or commit phase)
130
- mountEventResponder(responder, listenerProps, fiber, respondersMap);
135
+ mountEventResponder(
136
+ responder,
137
+ listenerProps,
138
+ fiber,
139
+ respondersMap,
140
+ rootContainerInstance,
141
+ );
142
} else {
143
// Update (happens during commit phase only)
144
responderInstance.props = listenerProps;
@@ -135,7 +146,11 @@ function updateEventListener(
146
}
147
}
148
138
-export function updateEventListeners(listeners: any, fiber: Fiber): void {
149
+export function updateEventListeners(
150
+ listeners: any,
151
+ fiber: Fiber,
152
+ rootContainerInstance: null | Container,
153
+): void {
154
const visistedResponders = new Set();
155
let dependencies = fiber.dependencies;
156
if (listeners != null) {
@@ -153,10 +168,22 @@ export function updateEventListeners(listeners: any, fiber: Fiber): void {
168
if (isArray(listeners)) {
169
for (let i = 0, length = listeners.length; i < length; i++) {
170
const listener = listeners[i];
156
- updateEventListener(listener, fiber, visistedResponders, respondersMap);
171
+ updateEventListener(
172
+ listener,
173
+ fiber,
174
+ visistedResponders,
175
+ respondersMap,
176
+ rootContainerInstance,
177
+ );
178
}
179
} else {
159
- updateEventListener(listeners, fiber, visistedResponders, respondersMap);
180
+ updateEventListener(
181
+ listeners,
182
+ fiber,
183
+ visistedResponders,
184
+ respondersMap,
185
+ rootContainerInstance,
186
+ );
187
}
188
}
189
if (dependencies !== null) {
packages/react-reconciler/src/__tests__/ReactScope-test.internal.js
+25
-3
@@ -199,11 +199,11 @@ describe('ReactScope', () => {
199
});
200
201
it('event responders can be attached to scopes', () => {
202
+ let onKeyDown = jest.fn();
203
const TestScope = React.unstable_createScope((type, props) => true);
203
- const onKeyDown = jest.fn();
204
const ref = React.createRef();
205
const useKeyboard = require('react-events/keyboard').useKeyboard;
206
- const Component = () => {
206
+ let Component = () => {
207
const listener = useKeyboard({
208
onKeyDown,
209
});
@@ -215,7 +215,29 @@ describe('ReactScope', () => {
215
};
216
ReactDOM.render(<Component />, container);
217
218
- const target = createEventTarget(ref.current);
218
+ let target = createEventTarget(ref.current);
219
+ target.keydown({key: 'Q'});
220
+ expect(onKeyDown).toHaveBeenCalledTimes(1);
221
+ expect(onKeyDown).toHaveBeenCalledWith(
222
+ expect.objectContaining({key: 'Q', type: 'keydown'}),
223
+ );
224
+
225
+ onKeyDown = jest.fn();
226
+ Component = () => {
227
+ const listener = useKeyboard({
228
+ onKeyDown,
229
+ });
230
+ return (
231
+ <div>
232
+ <TestScope listeners={listener}>
233
+ <div ref={ref} />
234
+ </TestScope>
235
+ </div>
236
+ );
237
+ };
238
+ ReactDOM.render(<Component />, container);
239
+
240
+ target = createEventTarget(ref.current);
241
target.keydown({key: 'Q'});
242
expect(onKeyDown).toHaveBeenCalledTimes(1);
243
expect(onKeyDown).toHaveBeenCalledWith(