[Flare] Refinements to useEvent hook (#15955)
Dominic Gannaway committed
Jun 21, 2019 at 23:10 UTC
dc298fdf91b6056b097bce0eb05cfe90b873b158
8 files changed
+73
-56
packages/react-dom/src/events/DOMEventResponderSystem.js
+35
-29
@@ -84,6 +84,7 @@ type ResponderTimeout = {|
84
|};
85
86
type ResponderTimer = {|
87
+ isHook: boolean,
88
instance: ReactDOMEventComponentInstance,
89
func: () => void,
90
id: number,
@@ -117,6 +118,7 @@ let currentInstance: null | ReactDOMEventComponentInstance = null;
118
let currentEventQueue: null | EventQueue = null;
119
let currentTimerIDCounter = 0;
120
let currentDocument: null | Document = null;
121
+let currentlyInHook = false;
122
123
const eventResponderContext: ReactDOMResponderContext = {
124
dispatchEvent(
@@ -366,6 +368,7 @@ const eventResponderContext: ReactDOMResponderContext = {
368
currentTimers.set(delay, timeout);
369
}
370
timeout.timers.set(timerId, {
371
+ isHook: currentlyInHook,
372
instance: ((currentInstance: any): ReactDOMEventComponentInstance),
373
func,
374
id: timerId,
@@ -456,6 +459,9 @@ const eventResponderContext: ReactDOMResponderContext = {
459
validateResponderContext();
460
continueLocalPropagation = true;
461
},
462
+ isRespondingToHook() {
463
+ return currentlyInHook;
464
+ },
465
};
466
467
function collectFocusableElements(
@@ -535,9 +541,10 @@ function processTimers(
541
currentEventQueue = createEventQueue();
542
try {
543
for (let i = 0; i < timersArr.length; i++) {
538
- const {instance, func, id, timeStamp} = timersArr[i];
544
+ const {isHook, instance, func, id, timeStamp} = timersArr[i];
545
currentInstance = instance;
546
currentTimeStamp = timeStamp + delay;
547
+ currentlyInHook = isHook;
548
try {
549
func();
550
} finally {
@@ -763,9 +770,9 @@ function shouldSkipEventComponent(
770
eventResponderInstance: ReactDOMEventComponentInstance,
771
responder: ReactDOMEventResponder,
772
propagatedEventResponders: null | Set<ReactDOMEventResponder>,
766
- localPropagation: boolean,
773
+ isHook: boolean,
774
): boolean {
768
- if (propagatedEventResponders !== null && localPropagation) {
775
+ if (propagatedEventResponders !== null && !isHook) {
776
if (propagatedEventResponders.has(responder)) {
777
return true;
778
}
@@ -830,12 +837,7 @@ function traverseAndHandleEventResponderInstances(
837
// Capture target phase
838
for (i = length; i-- > 0; ) {
839
const targetEventResponderInstance = targetEventResponderInstances[i];
833
- const {
834
- localPropagation,
835
- props,
836
- responder,
837
- state,
838
- } = targetEventResponderInstance;
840
+ const {isHook, props, responder, state} = targetEventResponderInstance;
841
const eventListener = responder.onEventCapture;
842
if (eventListener !== undefined) {
843
if (
@@ -843,14 +845,15 @@ function traverseAndHandleEventResponderInstances(
845
targetEventResponderInstance,
846
((responder: any): ReactDOMEventResponder),
847
propagatedEventResponders,
846
- localPropagation,
848
+ isHook,
849
)
850
) {
851
continue;
852
}
853
currentInstance = targetEventResponderInstance;
854
+ currentlyInHook = isHook;
855
eventListener(responderEvent, eventResponderContext, props, state);
853
- if (localPropagation) {
856
+ if (!isHook) {
857
checkForLocalPropagationContinuation(
858
responder,
859
propagatedEventResponders,
@@ -863,12 +866,7 @@ function traverseAndHandleEventResponderInstances(
866
// Bubble target phase
867
for (i = 0; i < length; i++) {
868
const targetEventResponderInstance = targetEventResponderInstances[i];
866
- const {
867
- localPropagation,
868
- props,
869
- responder,
870
- state,
871
- } = targetEventResponderInstance;
869
+ const {isHook, props, responder, state} = targetEventResponderInstance;
870
const eventListener = responder.onEvent;
871
if (eventListener !== undefined) {
872
if (
@@ -876,14 +874,15 @@ function traverseAndHandleEventResponderInstances(
874
targetEventResponderInstance,
875
((responder: any): ReactDOMEventResponder),
876
propagatedEventResponders,
879
- localPropagation,
877
+ isHook,
878
)
879
) {
880
continue;
881
}
882
currentInstance = targetEventResponderInstance;
883
+ currentlyInHook = isHook;
884
eventListener(responderEvent, eventResponderContext, props, state);
886
- if (localPropagation) {
885
+ if (!isHook) {
886
checkForLocalPropagationContinuation(
887
responder,
888
propagatedEventResponders,
@@ -900,12 +899,7 @@ function traverseAndHandleEventResponderInstances(
899
if (length > 0) {
900
for (i = 0; i < length; i++) {
901
const rootEventResponderInstance = rootEventResponderInstances[i];
903
- const {
904
- localPropagation,
905
- props,
906
- responder,
907
- state,
908
- } = rootEventResponderInstance;
902
+ const {isHook, props, responder, state} = rootEventResponderInstance;
903
const eventListener = responder.onRootEvent;
904
if (eventListener !== undefined) {
905
if (
@@ -913,12 +907,13 @@ function traverseAndHandleEventResponderInstances(
907
rootEventResponderInstance,
908
responder,
909
null,
916
- localPropagation,
910
+ isHook,
911
)
912
) {
913
continue;
914
}
915
currentInstance = rootEventResponderInstance;
916
+ currentlyInHook = isHook;
917
eventListener(responderEvent, eventResponderContext, props, state);
918
}
919
}
@@ -928,19 +923,24 @@ function traverseAndHandleEventResponderInstances(
923
function triggerOwnershipListeners(): void {
924
const listeningInstances = Array.from(ownershipChangeListeners);
925
const previousInstance = currentInstance;
926
+ const previouslyInHook = currentlyInHook;
927
+ currentEventQueue = createEventQueue();
928
try {
929
for (let i = 0; i < listeningInstances.length; i++) {
930
const instance = listeningInstances[i];
934
- const {props, responder, state} = instance;
931
+ const {isHook, props, responder, state} = instance;
932
currentInstance = instance;
933
+ currentlyInHook = isHook;
934
const onOwnershipChange = ((responder: any): ReactDOMEventResponder)
935
.onOwnershipChange;
936
if (onOwnershipChange !== undefined) {
937
onOwnershipChange(eventResponderContext, props, state);
938
}
939
}
940
+ processEventQueue();
941
} finally {
942
currentInstance = previousInstance;
943
+ currentlyInHook = previouslyInHook;
944
}
945
}
946
@@ -953,11 +953,13 @@ export function mountEventResponder(
953
}
954
const onMount = responder.onMount;
955
if (onMount !== undefined) {
956
- let {props, state} = eventComponentInstance;
956
+ let {isHook, props, state} = eventComponentInstance;
957
currentEventQueue = createEventQueue();
958
currentInstance = eventComponentInstance;
959
+ currentlyInHook = isHook;
960
try {
961
onMount(eventResponderContext, props, state);
962
+ processEventQueue();
963
} finally {
964
currentEventQueue = null;
965
currentInstance = null;
@@ -972,11 +974,13 @@ export function unmountEventResponder(
974
const responder = ((eventComponentInstance.responder: any): ReactDOMEventResponder);
975
const onUnmount = responder.onUnmount;
976
if (onUnmount !== undefined) {
975
- let {props, state} = eventComponentInstance;
977
+ let {isHook, props, state} = eventComponentInstance;
978
currentEventQueue = createEventQueue();
979
currentInstance = eventComponentInstance;
980
+ currentlyInHook = isHook;
981
try {
982
onUnmount(eventResponderContext, props, state);
983
+ processEventQueue();
984
} finally {
985
currentEventQueue = null;
986
currentInstance = null;
@@ -1030,6 +1034,7 @@ export function dispatchEventForResponderEventSystem(
1034
const previousTimers = currentTimers;
1035
const previousTimeStamp = currentTimeStamp;
1036
const previousDocument = currentDocument;
1037
+ const previouslyInHook = currentlyInHook;
1038
currentTimers = null;
1039
currentEventQueue = createEventQueue();
1040
currentDocument = (nativeEventTarget: any).ownerDocument;
@@ -1050,6 +1055,7 @@ export function dispatchEventForResponderEventSystem(
1055
currentEventQueue = previousEventQueue;
1056
currentTimeStamp = previousTimeStamp;
1057
currentDocument = previousDocument;
1058
+ currentlyInHook = previouslyInHook;
1059
}
1060
}
1061
}
packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js
+14
-6
@@ -1067,11 +1067,12 @@ describe('DOMEventResponderSystem', () => {
1067
};
1068
context.dispatchEvent(fooEvent, props.onFoo, DiscreteEvent);
1069
}
1070
+ eventLogs.push(context.isRespondingToHook() ? '[hook]' : '[component]');
1071
},
1072
});
1073
1074
const Test = () => {
1074
- React.unstable_useEvent(EventComponent.responder, {
1075
+ React.unstable_useEvent(EventComponent, {
1076
onFoo: e => eventLogs.push('hook'),
1077
});
1078
return (
@@ -1083,13 +1084,13 @@ describe('DOMEventResponderSystem', () => {
1084
1085
ReactDOM.render(<Test />, container);
1086
buttonRef.current.dispatchEvent(createEvent('foo'));
1086
- expect(eventLogs).toEqual(['prop', 'hook']);
1087
+ expect(eventLogs).toEqual(['[component]', '[hook]', 'prop', 'hook']);
1088
1089
// Clear events
1090
eventLogs.length = 0;
1091
1092
const Test2 = () => {
1092
- React.unstable_useEvent(EventComponent.responder, {
1093
+ React.unstable_useEvent(EventComponent, {
1094
onFoo: e => eventLogs.push('hook'),
1095
});
1096
return <button ref={buttonRef} />;
@@ -1101,10 +1102,10 @@ describe('DOMEventResponderSystem', () => {
1102
expect(eventLogs).toEqual([]);
1103
1104
const Test3 = () => {
1104
- React.unstable_useEvent(EventComponent.responder, {
1105
+ React.unstable_useEvent(EventComponent, {
1106
onFoo: e => eventLogs.push('hook 2a'),
1107
});
1107
- React.unstable_useEvent(EventComponent.responder, {
1108
+ React.unstable_useEvent(EventComponent, {
1109
onFoo: e => eventLogs.push('hook 2b'),
1110
});
1111
return (
@@ -1118,6 +1119,13 @@ describe('DOMEventResponderSystem', () => {
1119
1120
ReactDOM.render(<Test3 />, container);
1121
buttonRef.current.dispatchEvent(createEvent('foo'));
1121
- expect(eventLogs).toEqual(['prop 2', 'hook 2a', 'hook 2b']);
1122
+ expect(eventLogs).toEqual([
1123
+ '[component]',
1124
+ '[hook]',
1125
+ '[hook]',
1126
+ 'prop 2',
1127
+ 'hook 2a',
1128
+ 'hook 2b',
1129
+ ]);
1130
});
1131
});
packages/react-reconciler/src/ReactFiberCompleteWork.js
+1
-1
@@ -1041,7 +1041,7 @@ function completeWork(
1041
responder,
1042
rootContainerInstance,
1043
responderState || {},
1044
- true,
1044
+ false,
1045
);
1046
markUpdate(workInProgress);
1047
} else {
packages/react-reconciler/src/ReactFiberEvents.js
+6
-4
@@ -9,6 +9,7 @@
9
10
import type {Fiber, Dependencies} from './ReactFiber';
11
import type {
12
+ ReactEventComponent,
13
ReactEventResponder,
14
ReactEventComponentInstance,
15
} from 'shared/ReactTypes';
@@ -32,9 +33,10 @@ export function prepareToReadEventComponents(workInProgress: Fiber): void {
33
}
34
35
export function updateEventComponentInstance<T, E, C>(
35
- responder: ReactEventResponder<T, E, C>,
36
+ eventComponent: ReactEventComponent<T, E, C>,
37
props: Object,
38
): void {
39
+ const responder = eventComponent.responder;
40
invariant(
41
responder.allowEventHooks,
42
'The "%s" event responder cannot be used via the "useEvent" hook.',
@@ -67,7 +69,7 @@ export function updateEventComponentInstance<T, E, C>(
69
responder,
70
null,
71
responderState || {},
70
- false,
72
+ true,
73
);
74
events.push(eventComponentInstance);
75
currentEventComponentInstanceIndex++;
@@ -85,11 +87,11 @@ export function createEventComponentInstance<T, E, C>(
87
responder: ReactEventResponder<T, E, C>,
88
rootInstance: mixed,
89
state: Object,
88
- localPropagation: boolean,
90
+ isHook: boolean,
91
): ReactEventComponentInstance<T, E, C> {
92
return {
93
currentFiber,
92
- localPropagation,
94
+ isHook,
95
props,
96
responder,
97
rootEventTypes: null,
packages/react-reconciler/src/ReactFiberHooks.js
+12
-12
@@ -7,7 +7,7 @@
7
* @flow
8
*/
9
10
-import type {ReactEventResponder, ReactContext} from 'shared/ReactTypes';
10
+import type {ReactEventComponent, ReactContext} from 'shared/ReactTypes';
11
import type {SideEffectTag} from 'shared/ReactSideEffectTags';
12
import type {Fiber} from './ReactFiber';
13
import type {ExpirationTime} from './ReactFiberExpirationTime';
@@ -83,7 +83,7 @@ export type Dispatcher = {
83
): void,
84
useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void,
85
useEvent<T, E, C>(
86
- responder: ReactEventResponder<T, E, C>,
86
+ eventComponent: ReactEventComponent<T, E, C>,
87
props: Object,
88
): void,
89
};
@@ -1399,10 +1399,10 @@ if (__DEV__) {
1399
mountHookTypesDev();
1400
return mountDebugValue(value, formatterFn);
1401
},
1402
- useEvent<T, E, C>(responder: ReactEventResponder<T, E, C>, props) {
1402
+ useEvent<T, E, C>(eventComponent: ReactEventComponent<T, E, C>, props) {
1403
currentHookNameInDev = 'useEvent';
1404
mountHookTypesDev();
1405
- updateEventComponentInstance(responder, props);
1405
+ updateEventComponentInstance(eventComponent, props);
1406
},
1407
};
1408
@@ -1501,10 +1501,10 @@ if (__DEV__) {
1501
updateHookTypesDev();
1502
return mountDebugValue(value, formatterFn);
1503
},
1504
- useEvent<T, E, C>(responder: ReactEventResponder<T, E, C>, props) {
1504
+ useEvent<T, E, C>(eventComponent: ReactEventComponent<T, E, C>, props) {
1505
currentHookNameInDev = 'useEvent';
1506
updateHookTypesDev();
1507
- updateEventComponentInstance(responder, props);
1507
+ updateEventComponentInstance(eventComponent, props);
1508
},
1509
};
1510
@@ -1603,10 +1603,10 @@ if (__DEV__) {
1603
updateHookTypesDev();
1604
return updateDebugValue(value, formatterFn);
1605
},
1606
- useEvent<T, E, C>(responder: ReactEventResponder<T, E, C>, props) {
1606
+ useEvent<T, E, C>(eventComponent: ReactEventComponent<T, E, C>, props) {
1607
currentHookNameInDev = 'useEvent';
1608
updateHookTypesDev();
1609
- updateEventComponentInstance(responder, props);
1609
+ updateEventComponentInstance(eventComponent, props);
1610
},
1611
};
1612
@@ -1716,11 +1716,11 @@ if (__DEV__) {
1716
mountHookTypesDev();
1717
return mountDebugValue(value, formatterFn);
1718
},
1719
- useEvent<T, E, C>(responder: ReactEventResponder<T, E, C>, props) {
1719
+ useEvent<T, E, C>(eventComponent: ReactEventComponent<T, E, C>, props) {
1720
currentHookNameInDev = 'useEvent';
1721
warnInvalidHookAccess();
1722
mountHookTypesDev();
1723
- updateEventComponentInstance(responder, props);
1723
+ updateEventComponentInstance(eventComponent, props);
1724
},
1725
};
1726
@@ -1830,11 +1830,11 @@ if (__DEV__) {
1830
updateHookTypesDev();
1831
return updateDebugValue(value, formatterFn);
1832
},
1833
- useEvent<T, E, C>(responder: ReactEventResponder<T, E, C>, props) {
1833
+ useEvent<T, E, C>(eventComponent: ReactEventComponent<T, E, C>, props) {
1834
currentHookNameInDev = 'useEvent';
1835
warnInvalidHookAccess();
1836
updateHookTypesDev();
1837
- updateEventComponentInstance(responder, props);
1837
+ updateEventComponentInstance(eventComponent, props);
1838
},
1839
};
1840
}
packages/react/src/ReactHooks.js
+3
-3
@@ -7,7 +7,7 @@
7
* @flow
8
*/
9
10
-import type {ReactEventResponder, ReactContext} from 'shared/ReactTypes';
10
+import type {ReactEventComponent, ReactContext} from 'shared/ReactTypes';
11
import invariant from 'shared/invariant';
12
import warning from 'shared/warning';
13
@@ -139,9 +139,9 @@ export function useDebugValue(value: any, formatterFn: ?(value: any) => any) {
139
export const emptyObject = {};
140
141
export function useEvent<T, E, C>(
142
- responder: ReactEventResponder<T, E, C>,
142
+ eventComponent: ReactEventComponent<T, E, C>,
143
props: null | Object,
144
) {
145
const dispatcher = resolveDispatcher();
146
- return dispatcher.useEvent(responder, props || emptyObject);
146
+ return dispatcher.useEvent(eventComponent, props || emptyObject);
147
}
packages/shared/ReactDOMTypes.js
+1
@@ -84,4 +84,5 @@ export type ReactDOMResponderContext = {
84
deep: boolean,
85
) => boolean,
86
continueLocalPropagation(): void,
87
+ isRespondingToHook(): boolean,
88
};
packages/shared/ReactTypes.js
+1
-1
@@ -83,7 +83,7 @@ export type RefObject = {|
83
84
export type ReactEventComponentInstance<T, E, C> = {|
85
currentFiber: mixed,
86
- localPropagation: boolean,
86
+ isHook: boolean,
87
props: Object,
88
responder: ReactEventResponder<T, E, C>,
89
rootEventTypes: null | Set<string>,