Skip to content

Commit 7f2ca55

Browse files
committed
ReactDOM.useEvent: Add more scaffolding for useEvent hook
Add test Fix Address feedback fix
1 parent a3bf668 commit 7f2ca55

File tree

10 files changed

+203
-1
lines changed

10 files changed

+203
-1
lines changed

packages/react-debug-tools/src/ReactDebugHooks.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,16 @@ function useTransition(
256256
return [callback => {}, false];
257257
}
258258

259+
const noOp = () => {};
260+
261+
function useEvent(event: any): any {
262+
hookLog.push({primitive: 'Event', stackError: new Error(), value: event});
263+
return {
264+
clear: noOp,
265+
setListener: noOp,
266+
};
267+
}
268+
259269
function useDeferredValue<T>(value: T, config: TimeoutConfig | null | void): T {
260270
// useDeferredValue() composes multiple hooks internally.
261271
// Advance the current hook index the same number of times
@@ -285,6 +295,7 @@ const Dispatcher: DispatcherType = {
285295
useResponder,
286296
useTransition,
287297
useDeferredValue,
298+
useEvent,
288299
};
289300

290301
// Inspect

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ import type {
5151
ReactDOMEventResponder,
5252
ReactDOMEventResponderInstance,
5353
ReactDOMFundamentalComponentInstance,
54+
ReactDOMListener,
55+
ReactDOMListenerEvent,
56+
ReactDOMListenerMap,
5457
} from 'shared/ReactDOMTypes';
5558
import {
5659
mountEventResponder,
@@ -70,6 +73,10 @@ import {
7073
IS_PASSIVE,
7174
} from 'legacy-events/EventSystemFlags';
7275

76+
export type ReactListenerEvent = ReactDOMListenerEvent;
77+
export type ReactListenerMap = ReactDOMListenerMap;
78+
export type ReactListener = ReactDOMListener;
79+
7380
export type Type = string;
7481
export type Props = {
7582
autoFocus?: boolean,

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type {EventSystemFlags} from 'legacy-events/EventSystemFlags';
1313
import type {Fiber} from 'react-reconciler/src/ReactFiber';
1414
import type {PluginModule} from 'legacy-events/PluginModuleType';
1515
import type {ReactSyntheticEvent} from 'legacy-events/ReactSyntheticEventType';
16+
import type {ReactDOMListener} from 'shared/ReactDOMTypes';
1617

1718
import {registrationNameDependencies} from 'legacy-events/EventPluginRegistry';
1819
import {batchedEventUpdates} from 'legacy-events/ReactGenericBatching';
@@ -296,3 +297,11 @@ export function dispatchEventForPluginEventSystem(
296297
),
297298
);
298299
}
300+
301+
export function attachElementListener(listener: ReactDOMListener): void {
302+
// TODO
303+
}
304+
305+
export function detachElementListener(listener: ReactDOMListener): void {
306+
// TODO
307+
}

packages/react-dom/src/events/__tests__/DOMModernPluginEventSystem-test.internal.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1039,4 +1039,35 @@ describe('DOMModernPluginEventSystem', () => {
10391039
expect(log).toEqual([]);
10401040
expect(onDivClick).toHaveBeenCalledTimes(0);
10411041
});
1042+
1043+
describe('ReactDOM.useEvent', () => {
1044+
beforeEach(() => {
1045+
jest.resetModules();
1046+
ReactFeatureFlags = require('shared/ReactFeatureFlags');
1047+
ReactFeatureFlags.enableModernEventSystem = true;
1048+
ReactFeatureFlags.enableUseEventAPI = true;
1049+
1050+
React = require('react');
1051+
ReactDOM = require('react-dom');
1052+
Scheduler = require('scheduler');
1053+
ReactDOMServer = require('react-dom/server');
1054+
});
1055+
1056+
it('should create the same event listener map', () => {
1057+
let listenerMaps = [];
1058+
1059+
function Test() {
1060+
const listenerMap = ReactDOM.unstable_useEvent('click');
1061+
1062+
listenerMaps.push(listenerMap);
1063+
1064+
return <div />;
1065+
}
1066+
1067+
ReactDOM.render(<Test />, container);
1068+
ReactDOM.render(<Test />, container);
1069+
expect(listenerMaps.length).toEqual(2);
1070+
expect(listenerMaps[0]).toEqual(listenerMaps[1]);
1071+
});
1072+
});
10421073
});

packages/react-dom/src/server/ReactPartialRendererHooks.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,13 @@ function useTransition(
474474
return [startTransition, false];
475475
}
476476

477+
function useEvent(event: any): any {
478+
return {
479+
clear: noop,
480+
setListener: noop,
481+
};
482+
}
483+
477484
function noop(): void {}
478485

479486
export let currentThreadID: ThreadID = 0;
@@ -500,4 +507,5 @@ export const Dispatcher: DispatcherType = {
500507
useResponder,
501508
useDeferredValue,
502509
useTransition,
510+
useEvent,
503511
};

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ export type UpdatePayload = Object;
7676
export type TimeoutHandle = TimeoutID;
7777
export type NoTimeout = -1;
7878

79+
export type ReactListenerEvent = Object;
80+
export type ReactListenerMap = Object;
81+
export type ReactListener = Object;
82+
7983
// TODO: Remove this conditional once all changes have propagated.
8084
if (registerEventHandler) {
8185
/**

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ import ReactNativeFiberHostComponent from './ReactNativeFiberHostComponent';
2626

2727
const {get: getViewConfigForType} = ReactNativeViewConfigRegistry;
2828

29+
export type ReactListenerEvent = Object;
30+
export type ReactListenerMap = Object;
31+
export type ReactListener = Object;
32+
2933
export type Type = string;
3034
export type Props = Object;
3135
export type Container = number;

packages/react-reconciler/src/ReactFiberHooks.js

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,13 @@ import type {ExpirationTime} from './ReactFiberExpirationTime';
1717
import type {HookEffectTag} from './ReactHookEffectTags';
1818
import type {SuspenseConfig} from './ReactFiberSuspenseConfig';
1919
import type {ReactPriorityLevel} from './SchedulerWithReactIntegration';
20+
import type {
21+
ReactListenerEvent,
22+
ReactListenerMap,
23+
} from './ReactFiberHostConfig';
2024

2125
import ReactSharedInternals from 'shared/ReactSharedInternals';
26+
import {enableUseEventAPI} from 'shared/ReactFeatureFlags';
2227

2328
import {NoWork, Sync} from './ReactFiberExpirationTime';
2429
import {readContext} from './ReactFiberNewContext';
@@ -28,6 +33,7 @@ import {
2833
Passive as PassiveEffect,
2934
} from 'shared/ReactSideEffectTags';
3035
import {
36+
NoEffect as NoHookEffect,
3137
HasEffect as HookHasEffect,
3238
Layout as HookLayout,
3339
Passive as HookPassive,
@@ -97,6 +103,7 @@ export type Dispatcher = {|
97103
useTransition(
98104
config: SuspenseConfig | void | null,
99105
): [(() => void) => void, boolean],
106+
useEvent(event: ReactListenerEvent): ReactListenerMap,
100107
|};
101108

102109
type Update<S, A> = {|
@@ -129,7 +136,8 @@ export type HookType =
129136
| 'useDebugValue'
130137
| 'useResponder'
131138
| 'useDeferredValue'
132-
| 'useTransition';
139+
| 'useTransition'
140+
| 'useEvent';
133141

134142
let didWarnAboutMismatchedHooksForComponent;
135143
if (__DEV__) {
@@ -1369,6 +1377,77 @@ function dispatchAction<S, A>(
13691377
}
13701378
}
13711379

1380+
const noOpMount = () => {};
1381+
1382+
function mountEventListener(event: ReactListenerEvent): ReactListenerMap {
1383+
if (enableUseEventAPI) {
1384+
const hook = mountWorkInProgressHook();
1385+
1386+
const clear = () => {
1387+
// TODO
1388+
};
1389+
1390+
const reactListenerMap: ReactListenerMap = {
1391+
clear,
1392+
setListener(instance: EventTarget, callback: ?(Event) => void): void {
1393+
// TODO
1394+
},
1395+
};
1396+
// In order to clear up upon the hook unmounting,
1397+
// we ensure we set the effecrt tag so that we visit
1398+
// this effect in the commit phase, so we can handle
1399+
// clean-up accordingly.
1400+
currentlyRenderingFiber.effectTag |= UpdateEffect;
1401+
pushEffect(NoHookEffect, noOpMount, clear, null);
1402+
hook.memoizedState = [reactListenerMap, event, clear];
1403+
return reactListenerMap;
1404+
}
1405+
// To make Flow not complain
1406+
return (undefined: any);
1407+
}
1408+
1409+
function updateEventListener(event: ReactListenerEvent): ReactListenerMap {
1410+
if (enableUseEventAPI) {
1411+
const hook = updateWorkInProgressHook();
1412+
const [reactListenerMap, memoizedEvent, clear] = hook.memoizedState;
1413+
if (__DEV__) {
1414+
if (memoizedEvent.type !== event.type) {
1415+
console.warn(
1416+
'The event type argument passed to the useEvent() hook was different between renders.' +
1417+
' The event type is static and should never change between renders.',
1418+
);
1419+
}
1420+
if (memoizedEvent.capture !== event.capture) {
1421+
console.warn(
1422+
'The "capture" option passed to the useEvent() hook was different between renders.' +
1423+
' The "capture" option is static and should never change between renders.',
1424+
);
1425+
}
1426+
if (memoizedEvent.priority !== event.priority) {
1427+
console.warn(
1428+
'The "priority" option passed to the useEvent() hook was different between renders.' +
1429+
' The "priority" option is static and should never change between renders.',
1430+
);
1431+
}
1432+
if (memoizedEvent.passive !== event.passive) {
1433+
console.warn(
1434+
'The "passive" option passed to the useEvent() hook was different between renders.' +
1435+
' The "passive" option is static and should never change between renders.',
1436+
);
1437+
}
1438+
}
1439+
// In order to clear up upon the hook unmounting,
1440+
// we ensure we set the effecrt tag so that we visit
1441+
// this effect in the commit phase, so we can handle
1442+
// clean-up accordingly.
1443+
currentlyRenderingFiber.effectTag |= UpdateEffect;
1444+
pushEffect(NoHookEffect, noOpMount, clear, null);
1445+
return reactListenerMap;
1446+
}
1447+
// To make Flow not complain
1448+
return (undefined: any);
1449+
}
1450+
13721451
export const ContextOnlyDispatcher: Dispatcher = {
13731452
readContext,
13741453

@@ -1385,6 +1464,7 @@ export const ContextOnlyDispatcher: Dispatcher = {
13851464
useResponder: throwInvalidHookError,
13861465
useDeferredValue: throwInvalidHookError,
13871466
useTransition: throwInvalidHookError,
1467+
useEvent: throwInvalidHookError,
13881468
};
13891469

13901470
const HooksDispatcherOnMount: Dispatcher = {
@@ -1403,6 +1483,7 @@ const HooksDispatcherOnMount: Dispatcher = {
14031483
useResponder: createDeprecatedResponderListener,
14041484
useDeferredValue: mountDeferredValue,
14051485
useTransition: mountTransition,
1486+
useEvent: mountEventListener,
14061487
};
14071488

14081489
const HooksDispatcherOnUpdate: Dispatcher = {
@@ -1421,6 +1502,7 @@ const HooksDispatcherOnUpdate: Dispatcher = {
14211502
useResponder: createDeprecatedResponderListener,
14221503
useDeferredValue: updateDeferredValue,
14231504
useTransition: updateTransition,
1505+
useEvent: updateEventListener,
14241506
};
14251507

14261508
const HooksDispatcherOnRerender: Dispatcher = {
@@ -1439,6 +1521,7 @@ const HooksDispatcherOnRerender: Dispatcher = {
14391521
useResponder: createDeprecatedResponderListener,
14401522
useDeferredValue: rerenderDeferredValue,
14411523
useTransition: rerenderTransition,
1524+
useEvent: updateEventListener,
14421525
};
14431526

14441527
let HooksDispatcherOnMountInDEV: Dispatcher | null = null;
@@ -1588,6 +1671,11 @@ if (__DEV__) {
15881671
mountHookTypesDev();
15891672
return mountTransition(config);
15901673
},
1674+
useEvent(event: ReactListenerEvent): ReactListenerMap {
1675+
currentHookNameInDev = 'useEvent';
1676+
mountHookTypesDev();
1677+
return mountEventListener(event);
1678+
},
15911679
};
15921680

15931681
HooksDispatcherOnMountWithHookTypesInDEV = {
@@ -1705,6 +1793,11 @@ if (__DEV__) {
17051793
updateHookTypesDev();
17061794
return mountTransition(config);
17071795
},
1796+
useEvent(event: ReactListenerEvent): ReactListenerMap {
1797+
currentHookNameInDev = 'useEvent';
1798+
updateHookTypesDev();
1799+
return mountEventListener(event);
1800+
},
17081801
};
17091802

17101803
HooksDispatcherOnUpdateInDEV = {
@@ -1822,6 +1915,11 @@ if (__DEV__) {
18221915
updateHookTypesDev();
18231916
return updateTransition(config);
18241917
},
1918+
useEvent(event: ReactListenerEvent): ReactListenerMap {
1919+
currentHookNameInDev = 'useEvent';
1920+
updateHookTypesDev();
1921+
return updateEventListener(event);
1922+
},
18251923
};
18261924

18271925
HooksDispatcherOnRerenderInDEV = {
@@ -1939,6 +2037,11 @@ if (__DEV__) {
19392037
updateHookTypesDev();
19402038
return rerenderTransition(config);
19412039
},
2040+
useEvent(event: ReactListenerEvent): ReactListenerMap {
2041+
currentHookNameInDev = 'useEvent';
2042+
updateHookTypesDev();
2043+
return updateEventListener(event);
2044+
},
19422045
};
19432046

19442047
InvalidNestedHooksDispatcherOnMountInDEV = {
@@ -2070,6 +2173,12 @@ if (__DEV__) {
20702173
mountHookTypesDev();
20712174
return mountTransition(config);
20722175
},
2176+
useEvent(event: ReactListenerEvent): ReactListenerMap {
2177+
currentHookNameInDev = 'useEvent';
2178+
warnInvalidHookAccess();
2179+
mountHookTypesDev();
2180+
return mountEventListener(event);
2181+
},
20732182
};
20742183

20752184
InvalidNestedHooksDispatcherOnUpdateInDEV = {
@@ -2201,6 +2310,12 @@ if (__DEV__) {
22012310
updateHookTypesDev();
22022311
return updateTransition(config);
22032312
},
2313+
useEvent(event: ReactListenerEvent): ReactListenerMap {
2314+
currentHookNameInDev = 'useEvent';
2315+
warnInvalidHookAccess();
2316+
updateHookTypesDev();
2317+
return updateEventListener(event);
2318+
},
22042319
};
22052320

22062321
InvalidNestedHooksDispatcherOnRerenderInDEV = {
@@ -2332,5 +2447,11 @@ if (__DEV__) {
23322447
updateHookTypesDev();
23332448
return rerenderTransition(config);
23342449
},
2450+
useEvent(event: ReactListenerEvent): ReactListenerMap {
2451+
currentHookNameInDev = 'useEvent';
2452+
warnInvalidHookAccess();
2453+
updateHookTypesDev();
2454+
return updateEventListener(event);
2455+
},
23352456
};
23362457
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ export opaque type ChildSet = mixed; // eslint-disable-line no-undef
3838
export opaque type TimeoutHandle = mixed; // eslint-disable-line no-undef
3939
export opaque type NoTimeout = mixed; // eslint-disable-line no-undef
4040
export type EventResponder = any;
41+
export type ReactListenerEvent = Object;
42+
export type ReactListenerMap = Object;
43+
export type ReactListener = Object;
4144

4245
export const getPublicInstance = $$$hostConfig.getPublicInstance;
4346
export const getRootHostContext = $$$hostConfig.getRootHostContext;

0 commit comments

Comments
 (0)