@samitouri / QOS-React / commits / 4064ea9fa6

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,