Experimental event API: Support EventComponent onUnmount responder callback (#15335)
Dominic Gannaway committed
Apr 6, 2019 at 08:16 UTC
4064ea9fa6387c92a985b52bfc66746f81ccd4fd
13 files changed
+122
-8
packages/react-art/src/ReactARTHostConfig.js
+7
@@ -442,6 +442,13 @@ export function unhideTextInstance(textInstance, text): void {
442
export function handleEventComponent(
443
eventResponder: ReactEventResponder,
444
rootContainerInstance: Container,
445
+) {
446
+ throw new Error('Not yet implemented.');
447
+}
448
+
449
+export function unmountEventComponent(
450
+ eventResponder: ReactEventResponder,
451
+ rootContainerInstance: Container,
452
internalInstanceHandle: Object,
453
): void {
454
throw new Error('Not yet implemented.');
packages/react-dom/src/client/ReactDOMHostConfig.js
+12
-1
@@ -45,6 +45,7 @@ import dangerousStyleValue from '../shared/dangerousStyleValue';
45
46
import type {DOMContainer} from './ReactDOM';
47
import type {ReactEventResponder} from 'shared/ReactTypes';
48
+import {unmountEventResponder} from '../events/DOMEventResponderSystem';
49
import {REACT_EVENT_TARGET_TOUCH_HIT} from 'shared/ReactSymbols';
50
import {canUseDOM} from 'shared/ExecutionEnvironment';
51
@@ -890,7 +891,6 @@ export function didNotFindHydratableSuspenseInstance(
891
export function handleEventComponent(
892
eventResponder: ReactEventResponder,
893
rootContainerInstance: Container,
893
- internalInstanceHandle: Object,
894
): void {
895
if (enableEventAPI) {
896
const rootElement = rootContainerInstance.ownerDocument;
@@ -901,6 +901,17 @@ export function handleEventComponent(
901
}
902
}
903
904
+export function unmountEventComponent(
905
+ eventResponder: ReactEventResponder,
906
+ rootContainerInstance: Container,
907
+ internalInstanceHandle: Object,
908
+): void {
909
+ if (enableEventAPI) {
910
+ // TODO stop listening to targetEventTypes
911
+ unmountEventResponder(eventResponder, internalInstanceHandle);
912
+ }
913
+}
914
+
915
export function getEventTargetChildElement(
916
type: Symbol | number,
917
props: Props,
packages/react-dom/src/events/DOMEventResponderSystem.js
+35
-1
@@ -364,10 +364,17 @@ function handleTopLevelType(
364
if (state === null && responder.createInitialState !== undefined) {
365
state = fiber.stateNode.state = responder.createInitialState(props);
366
}
367
+ const previousFiber = currentFiber;
368
+ const previousResponder = currentResponder;
369
currentFiber = fiber;
370
currentResponder = responder;
371
370
- responder.onEvent(responderEvent, eventResponderContext, props, state);
372
+ try {
373
+ responder.onEvent(responderEvent, eventResponderContext, props, state);
374
+ } finally {
375
+ currentFiber = previousFiber;
376
+ currentResponder = previousResponder;
377
+ }
378
}
379
380
export function runResponderEventsInBatch(
@@ -413,3 +420,30 @@ export function runResponderEventsInBatch(
420
processEventQueue();
421
}
422
}
423
+
424
+export function unmountEventResponder(
425
+ responder: ReactEventResponder,
426
+ fiber: Fiber,
427
+): void {
428
+ const onUnmount = responder.onUnmount;
429
+ if (onUnmount !== undefined) {
430
+ let {props, state} = fiber.stateNode;
431
+ const previousEventQueue = currentEventQueue;
432
+ const previousFiber = currentFiber;
433
+ const previousResponder = currentResponder;
434
+ currentEventQueue = createEventQueue();
435
+ currentFiber = fiber;
436
+ currentResponder = responder;
437
+ try {
438
+ onUnmount(eventResponderContext, props, state);
439
+ } finally {
440
+ currentEventQueue = previousEventQueue;
441
+ currentFiber = previousFiber;
442
+ currentResponder = previousResponder;
443
+ }
444
+ }
445
+ if (currentOwner === fiber) {
446
+ // TODO fire owner changed callback
447
+ currentOwner = null;
448
+ }
449
+}
packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js
+24
-1
@@ -13,10 +13,11 @@ let React;
13
let ReactFeatureFlags;
14
let ReactDOM;
15
16
-function createReactEventComponent(targetEventTypes, onEvent) {
16
+function createReactEventComponent(targetEventTypes, onEvent, onUnmount) {
17
const testEventResponder = {
18
targetEventTypes,
19
onEvent,
20
+ onUnmount,
21
};
22
23
return {
@@ -316,4 +317,26 @@ describe('DOMEventResponderSystem', () => {
317
318
expect(eventLog).toEqual(['press', 'longpress', 'longpresschange']);
319
});
320
+
321
+ it('the event responder onUnmount() function should fire', () => {
322
+ let onUnmountFired = 0;
323
+
324
+ const EventComponent = createReactEventComponent(
325
+ [],
326
+ (event, context, props) => {},
327
+ () => {
328
+ onUnmountFired++;
329
+ },
330
+ );
331
+
332
+ const Test = () => (
333
+ <EventComponent>
334
+ <button />
335
+ </EventComponent>
336
+ );
337
+
338
+ ReactDOM.render(<Test />, container);
339
+ ReactDOM.render(null, container);
340
+ expect(onUnmountFired).toEqual(1);
341
+ });
342
});
packages/react-events/src/Press.js
+1
-1
@@ -209,6 +209,7 @@ function unmountResponder(
209
): void {
210
if (state.isPressed) {
211
state.isPressed = false;
212
+ context.removeRootEventTypes(rootEventTypes);
213
dispatchPressEndEvents(context, props, state);
214
if (state.longPressTimeout !== null) {
215
clearTimeout(state.longPressTimeout);
@@ -429,7 +430,6 @@ const PressResponder = {
430
}
431
}
432
},
432
- // TODO This method doesn't work as of yet
433
onUnmount(context: ResponderContext, props: PressProps, state: PressState) {
434
unmountResponder(context, props, state);
435
},
packages/react-native-renderer/src/ReactFabricHostConfig.js
+7
@@ -437,6 +437,13 @@ export function replaceContainerChildren(
437
export function handleEventComponent(
438
eventResponder: ReactEventResponder,
439
rootContainerInstance: Container,
440
+) {
441
+ throw new Error('Not yet implemented.');
442
+}
443
+
444
+export function unmountEventComponent(
445
+ eventResponder: ReactEventResponder,
446
+ rootContainerInstance: Container,
447
internalInstanceHandle: Object,
448
): void {
449
throw new Error('Not yet implemented.');
packages/react-native-renderer/src/ReactNativeHostConfig.js
+8
-1
@@ -496,11 +496,18 @@ export function unhideTextInstance(
496
export function handleEventComponent(
497
eventResponder: ReactEventResponder,
498
rootContainerInstance: Container,
499
- internalInstanceHandle: Object,
499
) {
500
throw new Error('Not yet implemented.');
501
}
502
503
+export function unmountEventComponent(
504
+ eventResponder: ReactEventResponder,
505
+ rootContainerInstance: Container,
506
+ internalInstanceHandle: Object,
507
+): void {
508
+ throw new Error('Not yet implemented.');
509
+}
510
+
511
export function getEventTargetChildElement(
512
type: Symbol | number,
513
props: Props,
packages/react-noop-renderer/src/createReactNoop.js
+5
-1
@@ -431,7 +431,11 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
431
isPrimaryRenderer: true,
432
supportsHydration: false,
433
434
- handleEventComponent() {
434
+ handleEventComponent(): void {
435
+ // NO-OP
436
+ },
437
+
438
+ unmountEventComponent(): void {
439
// NO-OP
440
},
441
packages/react-reconciler/src/ReactFiber.js
+1
@@ -626,6 +626,7 @@ export function createFiberFromEventComponent(
626
fiber.stateNode = {
627
context: null,
628
props: pendingProps,
629
+ rootInstance: null,
630
state: null,
631
};
632
fiber.expirationTime = expirationTime;
packages/react-reconciler/src/ReactFiberCommitWork.js
+10
@@ -44,6 +44,7 @@ import {
44
IncompleteClassComponent,
45
MemoComponent,
46
SimpleMemoComponent,
47
+ EventComponent,
48
EventTarget,
49
} from 'shared/ReactWorkTags';
50
import {
@@ -92,6 +93,7 @@ import {
93
hideTextInstance,
94
unhideInstance,
95
unhideTextInstance,
96
+ unmountEventComponent,
97
commitEventTarget,
98
} from './ReactFiberHostConfig';
99
import {
@@ -745,6 +747,14 @@ function commitUnmount(current: Fiber): void {
747
}
748
return;
749
}
750
+ case EventComponent: {
751
+ if (enableEventAPI) {
752
+ const rootContainerInstance = current.stateNode.rootInstance;
753
+ const responder = current.type.responder;
754
+ unmountEventComponent(responder, rootContainerInstance, current);
755
+ current.stateNode = null;
756
+ }
757
+ }
758
}
759
}
760
packages/react-reconciler/src/ReactFiberCompleteWork.js
+3
-1
@@ -776,7 +776,9 @@ function completeWork(
776
const responder = workInProgress.type.responder;
777
// Update the props on the event component state node
778
workInProgress.stateNode.props = newProps;
779
- handleEventComponent(responder, rootContainerInstance, workInProgress);
779
+ // Update the root container, so we can properly unmount events at some point
780
+ workInProgress.stateNode.rootInstance = rootContainerInstance;
781
+ handleEventComponent(responder, rootContainerInstance);
782
}
783
break;
784
}
packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js
+1
@@ -86,6 +86,7 @@ export const hideInstance = $$$hostConfig.hideInstance;
86
export const hideTextInstance = $$$hostConfig.hideTextInstance;
87
export const unhideInstance = $$$hostConfig.unhideInstance;
88
export const unhideTextInstance = $$$hostConfig.unhideTextInstance;
89
+export const unmountEventComponent = $$$hostConfig.unmountEventComponent;
90
export const commitTouchHitTargetUpdate =
91
$$$hostConfig.commitTouchHitTargetUpdate;
92
export const commitEventTarget = $$$hostConfig.commitEventTarget;
packages/react-test-renderer/src/ReactTestHostConfig.js
+8
-1
@@ -330,7 +330,6 @@ export function unhideTextInstance(
330
export function handleEventComponent(
331
eventResponder: ReactEventResponder,
332
rootContainerInstance: Container,
333
- internalInstanceHandle: Object,
333
): void {
334
// noop
335
}
@@ -364,6 +363,14 @@ export function getEventTargetChildElement(
363
return null;
364
}
365
366
+export function unmountEventComponent(
367
+ eventResponder: ReactEventResponder,
368
+ rootContainerInstance: Container,
369
+ internalInstanceHandle: Object,
370
+): void {
371
+ // TODO: add unmountEventComponent implementation
372
+}
373
+
374
export function handleEventTarget(
375
type: Symbol | number,
376
props: Props,