Skip to content

Commit 685ed56

Browse files
authored
Migrate useDeferredValue and useTransition (#17058)
Migrated useDeferredValue and useTransition from Facebook's www repo into ReactFiberHooks.
1 parent 0b61e26 commit 685ed56

File tree

9 files changed

+544
-19
lines changed

9 files changed

+544
-19
lines changed

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

+26-1
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ import type {
1414
ReactEventResponderListener,
1515
} from 'shared/ReactTypes';
1616
import type {Fiber} from 'react-reconciler/src/ReactFiber';
17-
import type {Hook} from 'react-reconciler/src/ReactFiberHooks';
17+
import type {Hook, TimeoutConfig} from 'react-reconciler/src/ReactFiberHooks';
1818
import type {Dispatcher as DispatcherType} from 'react-reconciler/src/ReactFiberHooks';
19+
import type {SuspenseConfig} from 'react-reconciler/src/ReactFiberSuspenseConfig';
1920

2021
import ErrorStackParser from 'error-stack-parser';
2122
import ReactSharedInternals from 'shared/ReactSharedInternals';
@@ -236,6 +237,28 @@ function useResponder(
236237
};
237238
}
238239

240+
function useTransition(
241+
config: SuspenseConfig | null | void,
242+
): [(() => void) => void, boolean] {
243+
nextHook();
244+
hookLog.push({
245+
primitive: 'Transition',
246+
stackError: new Error(),
247+
value: config,
248+
});
249+
return [callback => {}, false];
250+
}
251+
252+
function useDeferredValue<T>(value: T, config: TimeoutConfig | null | void): T {
253+
nextHook();
254+
hookLog.push({
255+
primitive: 'DeferredValue',
256+
stackError: new Error(),
257+
value,
258+
});
259+
return value;
260+
}
261+
239262
const Dispatcher: DispatcherType = {
240263
readContext,
241264
useCallback,
@@ -249,6 +272,8 @@ const Dispatcher: DispatcherType = {
249272
useRef,
250273
useState,
251274
useResponder,
275+
useTransition,
276+
useDeferredValue,
252277
};
253278

254279
// Inspect

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

+22-2
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@
77
* @flow
88
*/
99

10-
import type {Dispatcher as DispatcherType} from 'react-reconciler/src/ReactFiberHooks';
10+
import type {
11+
Dispatcher as DispatcherType,
12+
TimeoutConfig,
13+
} from 'react-reconciler/src/ReactFiberHooks';
1114
import type {ThreadID} from './ReactThreadIDAllocator';
1215
import type {
1316
ReactContext,
1417
ReactEventResponderListener,
1518
} from 'shared/ReactTypes';
16-
19+
import type {SuspenseConfig} from 'react-reconciler/src/ReactFiberSuspenseConfig';
1720
import {validateContextBounds} from './ReactPartialRendererContext';
1821

1922
import invariant from 'shared/invariant';
@@ -457,6 +460,21 @@ function useResponder(responder, props): ReactEventResponderListener<any, any> {
457460
};
458461
}
459462

463+
function useDeferredValue<T>(value: T, config: TimeoutConfig | null | void): T {
464+
resolveCurrentlyRenderingComponent();
465+
return value;
466+
}
467+
468+
function useTransition(
469+
config: SuspenseConfig | null | void,
470+
): [(callback: () => void) => void, boolean] {
471+
resolveCurrentlyRenderingComponent();
472+
const startTransition = callback => {
473+
callback();
474+
};
475+
return [startTransition, false];
476+
}
477+
460478
function noop(): void {}
461479

462480
export let currentThreadID: ThreadID = 0;
@@ -481,4 +499,6 @@ export const Dispatcher: DispatcherType = {
481499
// Debugging effect
482500
useDebugValue: noop,
483501
useResponder,
502+
useDeferredValue,
503+
useTransition,
484504
};

packages/react-reconciler/src/ReactFiberHooks.js

+173-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import type {HookEffectTag} from './ReactHookEffectTags';
1919
import type {SuspenseConfig} from './ReactFiberSuspenseConfig';
2020
import type {ReactPriorityLevel} from './SchedulerWithReactIntegration';
2121

22+
import * as Scheduler from 'scheduler';
2223
import ReactSharedInternals from 'shared/ReactSharedInternals';
2324

2425
import {NoWork} from './ReactFiberExpirationTime';
@@ -54,7 +55,7 @@ import {markWorkInProgressReceivedUpdate} from './ReactFiberBeginWork';
5455
import {requestCurrentSuspenseConfig} from './ReactFiberSuspenseConfig';
5556
import {getCurrentPriorityLevel} from './SchedulerWithReactIntegration';
5657

57-
const {ReactCurrentDispatcher} = ReactSharedInternals;
58+
const {ReactCurrentDispatcher, ReactCurrentBatchConfig} = ReactSharedInternals;
5859

5960
export type Dispatcher = {
6061
readContext<T>(
@@ -92,6 +93,10 @@ export type Dispatcher = {
9293
responder: ReactEventResponder<E, C>,
9394
props: Object,
9495
): ReactEventResponderListener<E, C>,
96+
useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T,
97+
useTransition(
98+
config: SuspenseConfig | void | null,
99+
): [(() => void) => void, boolean],
95100
};
96101

97102
type Update<S, A> = {
@@ -123,7 +128,9 @@ export type HookType =
123128
| 'useMemo'
124129
| 'useImperativeHandle'
125130
| 'useDebugValue'
126-
| 'useResponder';
131+
| 'useResponder'
132+
| 'useDeferredValue'
133+
| 'useTransition';
127134

128135
let didWarnAboutMismatchedHooksForComponent;
129136
if (__DEV__) {
@@ -152,6 +159,10 @@ export type FunctionComponentUpdateQueue = {
152159
lastEffect: Effect | null,
153160
};
154161

162+
export type TimeoutConfig = {|
163+
timeoutMs: number,
164+
|};
165+
155166
type BasicStateAction<S> = (S => S) | S;
156167

157168
type Dispatch<A> = A => void;
@@ -1117,6 +1128,96 @@ function updateMemo<T>(
11171128
return nextValue;
11181129
}
11191130

1131+
function mountDeferredValue<T>(
1132+
value: T,
1133+
config: TimeoutConfig | void | null,
1134+
): T {
1135+
const [prevValue, setValue] = mountState(value);
1136+
mountEffect(
1137+
() => {
1138+
Scheduler.unstable_next(() => {
1139+
const previousConfig = ReactCurrentBatchConfig.suspense;
1140+
ReactCurrentBatchConfig.suspense = config === undefined ? null : config;
1141+
try {
1142+
setValue(value);
1143+
} finally {
1144+
ReactCurrentBatchConfig.suspense = previousConfig;
1145+
}
1146+
});
1147+
},
1148+
[value, config],
1149+
);
1150+
return prevValue;
1151+
}
1152+
1153+
function updateDeferredValue<T>(
1154+
value: T,
1155+
config: TimeoutConfig | void | null,
1156+
): T {
1157+
const [prevValue, setValue] = updateState(value);
1158+
updateEffect(
1159+
() => {
1160+
Scheduler.unstable_next(() => {
1161+
const previousConfig = ReactCurrentBatchConfig.suspense;
1162+
ReactCurrentBatchConfig.suspense = config === undefined ? null : config;
1163+
try {
1164+
setValue(value);
1165+
} finally {
1166+
ReactCurrentBatchConfig.suspense = previousConfig;
1167+
}
1168+
});
1169+
},
1170+
[value, config],
1171+
);
1172+
return prevValue;
1173+
}
1174+
1175+
function mountTransition(
1176+
config: SuspenseConfig | void | null,
1177+
): [(() => void) => void, boolean] {
1178+
const [isPending, setPending] = mountState(false);
1179+
const startTransition = mountCallback(
1180+
callback => {
1181+
setPending(true);
1182+
Scheduler.unstable_next(() => {
1183+
const previousConfig = ReactCurrentBatchConfig.suspense;
1184+
ReactCurrentBatchConfig.suspense = config === undefined ? null : config;
1185+
try {
1186+
setPending(false);
1187+
callback();
1188+
} finally {
1189+
ReactCurrentBatchConfig.suspense = previousConfig;
1190+
}
1191+
});
1192+
},
1193+
[config, isPending],
1194+
);
1195+
return [startTransition, isPending];
1196+
}
1197+
1198+
function updateTransition(
1199+
config: SuspenseConfig | void | null,
1200+
): [(() => void) => void, boolean] {
1201+
const [isPending, setPending] = updateState(false);
1202+
const startTransition = updateCallback(
1203+
callback => {
1204+
setPending(true);
1205+
Scheduler.unstable_next(() => {
1206+
const previousConfig = ReactCurrentBatchConfig.suspense;
1207+
ReactCurrentBatchConfig.suspense = config === undefined ? null : config;
1208+
try {
1209+
setPending(false);
1210+
callback();
1211+
} finally {
1212+
ReactCurrentBatchConfig.suspense = previousConfig;
1213+
}
1214+
});
1215+
},
1216+
[config, isPending],
1217+
);
1218+
return [startTransition, isPending];
1219+
}
1220+
11201221
function dispatchAction<S, A>(
11211222
fiber: Fiber,
11221223
queue: UpdateQueue<S, A>,
@@ -1272,6 +1373,8 @@ export const ContextOnlyDispatcher: Dispatcher = {
12721373
useState: throwInvalidHookError,
12731374
useDebugValue: throwInvalidHookError,
12741375
useResponder: throwInvalidHookError,
1376+
useDeferredValue: throwInvalidHookError,
1377+
useTransition: throwInvalidHookError,
12751378
};
12761379

12771380
const HooksDispatcherOnMount: Dispatcher = {
@@ -1288,6 +1391,8 @@ const HooksDispatcherOnMount: Dispatcher = {
12881391
useState: mountState,
12891392
useDebugValue: mountDebugValue,
12901393
useResponder: createResponderListener,
1394+
useDeferredValue: mountDeferredValue,
1395+
useTransition: mountTransition,
12911396
};
12921397

12931398
const HooksDispatcherOnUpdate: Dispatcher = {
@@ -1304,6 +1409,8 @@ const HooksDispatcherOnUpdate: Dispatcher = {
13041409
useState: updateState,
13051410
useDebugValue: updateDebugValue,
13061411
useResponder: createResponderListener,
1412+
useDeferredValue: updateDeferredValue,
1413+
useTransition: updateTransition,
13071414
};
13081415

13091416
let HooksDispatcherOnMountInDEV: Dispatcher | null = null;
@@ -1441,6 +1548,18 @@ if (__DEV__) {
14411548
mountHookTypesDev();
14421549
return createResponderListener(responder, props);
14431550
},
1551+
useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T {
1552+
currentHookNameInDev = 'useDeferredValue';
1553+
mountHookTypesDev();
1554+
return mountDeferredValue(value, config);
1555+
},
1556+
useTransition(
1557+
config: SuspenseConfig | void | null,
1558+
): [(() => void) => void, boolean] {
1559+
currentHookNameInDev = 'useTransition';
1560+
mountHookTypesDev();
1561+
return mountTransition(config);
1562+
},
14441563
};
14451564

14461565
HooksDispatcherOnMountWithHookTypesInDEV = {
@@ -1546,6 +1665,18 @@ if (__DEV__) {
15461665
updateHookTypesDev();
15471666
return createResponderListener(responder, props);
15481667
},
1668+
useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T {
1669+
currentHookNameInDev = 'useDeferredValue';
1670+
updateHookTypesDev();
1671+
return mountDeferredValue(value, config);
1672+
},
1673+
useTransition(
1674+
config: SuspenseConfig | void | null,
1675+
): [(() => void) => void, boolean] {
1676+
currentHookNameInDev = 'useTransition';
1677+
updateHookTypesDev();
1678+
return mountTransition(config);
1679+
},
15491680
};
15501681

15511682
HooksDispatcherOnUpdateInDEV = {
@@ -1651,6 +1782,18 @@ if (__DEV__) {
16511782
updateHookTypesDev();
16521783
return createResponderListener(responder, props);
16531784
},
1785+
useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T {
1786+
currentHookNameInDev = 'useDeferredValue';
1787+
updateHookTypesDev();
1788+
return updateDeferredValue(value, config);
1789+
},
1790+
useTransition(
1791+
config: SuspenseConfig | void | null,
1792+
): [(() => void) => void, boolean] {
1793+
currentHookNameInDev = 'useTransition';
1794+
updateHookTypesDev();
1795+
return updateTransition(config);
1796+
},
16541797
};
16551798

16561799
InvalidNestedHooksDispatcherOnMountInDEV = {
@@ -1768,6 +1911,20 @@ if (__DEV__) {
17681911
mountHookTypesDev();
17691912
return createResponderListener(responder, props);
17701913
},
1914+
useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T {
1915+
currentHookNameInDev = 'useDeferredValue';
1916+
warnInvalidHookAccess();
1917+
mountHookTypesDev();
1918+
return mountDeferredValue(value, config);
1919+
},
1920+
useTransition(
1921+
config: SuspenseConfig | void | null,
1922+
): [(() => void) => void, boolean] {
1923+
currentHookNameInDev = 'useTransition';
1924+
warnInvalidHookAccess();
1925+
mountHookTypesDev();
1926+
return mountTransition(config);
1927+
},
17711928
};
17721929

17731930
InvalidNestedHooksDispatcherOnUpdateInDEV = {
@@ -1885,5 +2042,19 @@ if (__DEV__) {
18852042
updateHookTypesDev();
18862043
return createResponderListener(responder, props);
18872044
},
2045+
useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T {
2046+
currentHookNameInDev = 'useDeferredValue';
2047+
warnInvalidHookAccess();
2048+
updateHookTypesDev();
2049+
return updateDeferredValue(value, config);
2050+
},
2051+
useTransition(
2052+
config: SuspenseConfig | void | null,
2053+
): [(() => void) => void, boolean] {
2054+
currentHookNameInDev = 'useTransition';
2055+
warnInvalidHookAccess();
2056+
updateHookTypesDev();
2057+
return updateTransition(config);
2058+
},
18882059
};
18892060
}

packages/react-reconciler/src/ReactFiberWorkLoop.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -2930,7 +2930,7 @@ function flushSuspensePriorityWarningInDEV() {
29302930
'update to provide immediate feedback, and another update that ' +
29312931
'triggers the bulk of the changes.' +
29322932
'\n\n' +
2933-
'Refer to the documentation for useSuspenseTransition to learn how ' +
2933+
'Refer to the documentation for useTransition to learn how ' +
29342934
'to implement this pattern.',
29352935
// TODO: Add link to React docs with more information, once it exists
29362936
componentNames.sort().join(', '),

0 commit comments

Comments
 (0)