Skip to content

Commit 4064ea9

Browse files
authored
Experimental event API: Support EventComponent onUnmount responder callback (#15335)
1 parent 4fbbae8 commit 4064ea9

File tree

13 files changed

+122
-8
lines changed

13 files changed

+122
-8
lines changed

packages/react-art/src/ReactARTHostConfig.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,13 @@ export function unhideTextInstance(textInstance, text): void {
442442
export function handleEventComponent(
443443
eventResponder: ReactEventResponder,
444444
rootContainerInstance: Container,
445+
) {
446+
throw new Error('Not yet implemented.');
447+
}
448+
449+
export function unmountEventComponent(
450+
eventResponder: ReactEventResponder,
451+
rootContainerInstance: Container,
445452
internalInstanceHandle: Object,
446453
): void {
447454
throw new Error('Not yet implemented.');

packages/react-dom/src/client/ReactDOMHostConfig.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import dangerousStyleValue from '../shared/dangerousStyleValue';
4545

4646
import type {DOMContainer} from './ReactDOM';
4747
import type {ReactEventResponder} from 'shared/ReactTypes';
48+
import {unmountEventResponder} from '../events/DOMEventResponderSystem';
4849
import {REACT_EVENT_TARGET_TOUCH_HIT} from 'shared/ReactSymbols';
4950
import {canUseDOM} from 'shared/ExecutionEnvironment';
5051

@@ -890,7 +891,6 @@ export function didNotFindHydratableSuspenseInstance(
890891
export function handleEventComponent(
891892
eventResponder: ReactEventResponder,
892893
rootContainerInstance: Container,
893-
internalInstanceHandle: Object,
894894
): void {
895895
if (enableEventAPI) {
896896
const rootElement = rootContainerInstance.ownerDocument;
@@ -901,6 +901,17 @@ export function handleEventComponent(
901901
}
902902
}
903903

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+
904915
export function getEventTargetChildElement(
905916
type: Symbol | number,
906917
props: Props,

packages/react-dom/src/events/DOMEventResponderSystem.js

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,10 +364,17 @@ function handleTopLevelType(
364364
if (state === null && responder.createInitialState !== undefined) {
365365
state = fiber.stateNode.state = responder.createInitialState(props);
366366
}
367+
const previousFiber = currentFiber;
368+
const previousResponder = currentResponder;
367369
currentFiber = fiber;
368370
currentResponder = responder;
369371

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+
}
371378
}
372379

373380
export function runResponderEventsInBatch(
@@ -413,3 +420,30 @@ export function runResponderEventsInBatch(
413420
processEventQueue();
414421
}
415422
}
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

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ let React;
1313
let ReactFeatureFlags;
1414
let ReactDOM;
1515

16-
function createReactEventComponent(targetEventTypes, onEvent) {
16+
function createReactEventComponent(targetEventTypes, onEvent, onUnmount) {
1717
const testEventResponder = {
1818
targetEventTypes,
1919
onEvent,
20+
onUnmount,
2021
};
2122

2223
return {
@@ -316,4 +317,26 @@ describe('DOMEventResponderSystem', () => {
316317

317318
expect(eventLog).toEqual(['press', 'longpress', 'longpresschange']);
318319
});
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+
});
319342
});

packages/react-events/src/Press.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ function unmountResponder(
209209
): void {
210210
if (state.isPressed) {
211211
state.isPressed = false;
212+
context.removeRootEventTypes(rootEventTypes);
212213
dispatchPressEndEvents(context, props, state);
213214
if (state.longPressTimeout !== null) {
214215
clearTimeout(state.longPressTimeout);
@@ -429,7 +430,6 @@ const PressResponder = {
429430
}
430431
}
431432
},
432-
// TODO This method doesn't work as of yet
433433
onUnmount(context: ResponderContext, props: PressProps, state: PressState) {
434434
unmountResponder(context, props, state);
435435
},

packages/react-native-renderer/src/ReactFabricHostConfig.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,13 @@ export function replaceContainerChildren(
437437
export function handleEventComponent(
438438
eventResponder: ReactEventResponder,
439439
rootContainerInstance: Container,
440+
) {
441+
throw new Error('Not yet implemented.');
442+
}
443+
444+
export function unmountEventComponent(
445+
eventResponder: ReactEventResponder,
446+
rootContainerInstance: Container,
440447
internalInstanceHandle: Object,
441448
): void {
442449
throw new Error('Not yet implemented.');

packages/react-native-renderer/src/ReactNativeHostConfig.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -496,11 +496,18 @@ export function unhideTextInstance(
496496
export function handleEventComponent(
497497
eventResponder: ReactEventResponder,
498498
rootContainerInstance: Container,
499-
internalInstanceHandle: Object,
500499
) {
501500
throw new Error('Not yet implemented.');
502501
}
503502

503+
export function unmountEventComponent(
504+
eventResponder: ReactEventResponder,
505+
rootContainerInstance: Container,
506+
internalInstanceHandle: Object,
507+
): void {
508+
throw new Error('Not yet implemented.');
509+
}
510+
504511
export function getEventTargetChildElement(
505512
type: Symbol | number,
506513
props: Props,

packages/react-noop-renderer/src/createReactNoop.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,11 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
431431
isPrimaryRenderer: true,
432432
supportsHydration: false,
433433

434-
handleEventComponent() {
434+
handleEventComponent(): void {
435+
// NO-OP
436+
},
437+
438+
unmountEventComponent(): void {
435439
// NO-OP
436440
},
437441

packages/react-reconciler/src/ReactFiber.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,7 @@ export function createFiberFromEventComponent(
626626
fiber.stateNode = {
627627
context: null,
628628
props: pendingProps,
629+
rootInstance: null,
629630
state: null,
630631
};
631632
fiber.expirationTime = expirationTime;

packages/react-reconciler/src/ReactFiberCommitWork.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import {
4444
IncompleteClassComponent,
4545
MemoComponent,
4646
SimpleMemoComponent,
47+
EventComponent,
4748
EventTarget,
4849
} from 'shared/ReactWorkTags';
4950
import {
@@ -92,6 +93,7 @@ import {
9293
hideTextInstance,
9394
unhideInstance,
9495
unhideTextInstance,
96+
unmountEventComponent,
9597
commitEventTarget,
9698
} from './ReactFiberHostConfig';
9799
import {
@@ -745,6 +747,14 @@ function commitUnmount(current: Fiber): void {
745747
}
746748
return;
747749
}
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+
}
748758
}
749759
}
750760

packages/react-reconciler/src/ReactFiberCompleteWork.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -776,7 +776,9 @@ function completeWork(
776776
const responder = workInProgress.type.responder;
777777
// Update the props on the event component state node
778778
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);
780782
}
781783
break;
782784
}

packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export const hideInstance = $$$hostConfig.hideInstance;
8686
export const hideTextInstance = $$$hostConfig.hideTextInstance;
8787
export const unhideInstance = $$$hostConfig.unhideInstance;
8888
export const unhideTextInstance = $$$hostConfig.unhideTextInstance;
89+
export const unmountEventComponent = $$$hostConfig.unmountEventComponent;
8990
export const commitTouchHitTargetUpdate =
9091
$$$hostConfig.commitTouchHitTargetUpdate;
9192
export const commitEventTarget = $$$hostConfig.commitEventTarget;

packages/react-test-renderer/src/ReactTestHostConfig.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,6 @@ export function unhideTextInstance(
330330
export function handleEventComponent(
331331
eventResponder: ReactEventResponder,
332332
rootContainerInstance: Container,
333-
internalInstanceHandle: Object,
334333
): void {
335334
// noop
336335
}
@@ -364,6 +363,14 @@ export function getEventTargetChildElement(
364363
return null;
365364
}
366365

366+
export function unmountEventComponent(
367+
eventResponder: ReactEventResponder,
368+
rootContainerInstance: Container,
369+
internalInstanceHandle: Object,
370+
): void {
371+
// TODO: add unmountEventComponent implementation
372+
}
373+
367374
export function handleEventTarget(
368375
type: Symbol | number,
369376
props: Props,

0 commit comments

Comments
 (0)