Revert "Remove renderPhaseUpdates Map (#17484)" (#17623)
This reverts commit 8a347ed024159a5307172ea633a4561160f4a6b9.
Dan Abramov committed
Dec 16, 2019 at 19:03 UTC
f42431abe1a31a3798989a6477422c492cf8dac1
4 files changed
+133
-444
packages/react-reconciler/src/ReactFiberBeginWork.js
+2
-3
@@ -135,7 +135,7 @@ import {
135
calculateChangedBits,
136
scheduleWorkOnParentPath,
137
} from './ReactFiberNewContext';
138
-import {renderWithHooks, bailoutHooks} from './ReactFiberHooks';
138
+import {resetHooks, renderWithHooks, bailoutHooks} from './ReactFiberHooks';
139
import {stopProfilerTimerIfRunning} from './ReactProfilerTimer';
140
import {
141
getMaskedContext,
@@ -1318,8 +1318,7 @@ function mountIndeterminateComponent(
1318
workInProgress.tag = ClassComponent;
1319
1320
// Throw out any hooks that were used.
1321
- workInProgress.memoizedState = null;
1322
- workInProgress.updateQueue = null;
1321
+ resetHooks();
1322
1323
// Push context providers early to prevent context stack mismatches.
1324
// During mounting we don't know the child context yet as the instance doesn't exist.
packages/react-reconciler/src/ReactFiberHooks.js
+128
-394
@@ -192,7 +192,13 @@ let workInProgressHook: Hook | null = null;
192
193
// Whether an update was scheduled during the currently executing render pass.
194
let didScheduleRenderPhaseUpdate: boolean = false;
195
-
195
+// Lazily created map of render-phase updates
196
+let renderPhaseUpdates: Map<
197
+ UpdateQueue<any, any>,
198
+ Update<any, any>,
199
+> | null = null;
200
+// Counter to prevent infinite loops.
201
+let numberOfReRenders: number = 0;
202
const RE_RENDER_LIMIT = 25;
203
204
// In DEV, this is the name of the currently executing primitive hook
@@ -386,6 +392,8 @@ export function renderWithHooks(
392
// workInProgressHook = null;
393
394
// didScheduleRenderPhaseUpdate = false;
395
+ // renderPhaseUpdates = null;
396
+ // numberOfReRenders = 0;
397
398
// TODO Warn if no hooks are used at all during mount, then some are used during update.
399
// Currently we will identify the update render as a mount because memoizedState === null.
@@ -417,17 +425,8 @@ export function renderWithHooks(
425
let children = Component(props, refOrContext);
426
427
if (didScheduleRenderPhaseUpdate) {
420
- // Counter to prevent infinite loops.
421
- let numberOfReRenders: number = 0;
428
do {
429
didScheduleRenderPhaseUpdate = false;
424
-
425
- invariant(
426
- numberOfReRenders < RE_RENDER_LIMIT,
427
- 'Too many re-renders. React limits the number of renders to prevent ' +
428
- 'an infinite loop.',
429
- );
430
-
430
numberOfReRenders += 1;
431
if (__DEV__) {
432
// Even when hot reloading, allow dependencies to stabilize
@@ -447,11 +446,14 @@ export function renderWithHooks(
446
}
447
448
ReactCurrentDispatcher.current = __DEV__
450
- ? HooksDispatcherOnRerenderInDEV
451
- : HooksDispatcherOnRerender;
449
+ ? HooksDispatcherOnUpdateInDEV
450
+ : HooksDispatcherOnUpdate;
451
452
children = Component(props, refOrContext);
453
} while (didScheduleRenderPhaseUpdate);
454
+
455
+ renderPhaseUpdates = null;
456
+ numberOfReRenders = 0;
457
}
458
459
// We can assume the previous dispatcher is always this one, since we set it
@@ -481,6 +483,8 @@ export function renderWithHooks(
483
484
// These were reset above
485
// didScheduleRenderPhaseUpdate = false;
486
+ // renderPhaseUpdates = null;
487
+ // numberOfReRenders = 0;
488
489
invariant(
490
!didRenderTooFewHooks,
@@ -503,31 +507,14 @@ export function bailoutHooks(
507
}
508
}
509
506
-export function resetHooksAfterThrow(): void {
510
+export function resetHooks(): void {
511
// We can assume the previous dispatcher is always this one, since we set it
512
// at the beginning of the render phase and there's no re-entrancy.
513
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
514
511
- if (didScheduleRenderPhaseUpdate) {
512
- const current = (currentlyRenderingFiber: any).alternate;
513
- if (current !== null) {
514
- // There were render phase updates. These are only valid for this render
515
- // pass, which we are now aborting. Remove the updates from the queues so
516
- // they do not persist to the next render. We already did a single pass
517
- // through the whole list of hooks, so we know that any pending updates
518
- // must have been dispatched during the render phase. The ones that were
519
- // dispatched before we started rendering were already transferred to the
520
- // current hook's queue.
521
- let hook: Hook | null = current.memoizedState;
522
- while (hook !== null) {
523
- const queue = hook.queue;
524
- if (queue !== null) {
525
- queue.pending = null;
526
- }
527
- hook = hook.next;
528
- }
529
- }
530
- }
515
+ // This is used to reset the state of this module when a component throws.
516
+ // It's also called inside mountIndeterminateComponent if we determine the
517
+ // component is a module-style component.
518
519
renderExpirationTime = NoWork;
520
currentlyRenderingFiber = (null: any);
@@ -543,6 +530,8 @@ export function resetHooksAfterThrow(): void {
530
}
531
532
didScheduleRenderPhaseUpdate = false;
533
+ renderPhaseUpdates = null;
534
+ numberOfReRenders = 0;
535
}
536
537
function mountWorkInProgressHook(): Hook {
@@ -678,6 +667,49 @@ function updateReducer<S, I, A>(
667
668
queue.lastRenderedReducer = reducer;
669
670
+ if (numberOfReRenders > 0) {
671
+ // This is a re-render. Apply the new render phase updates to the previous
672
+ // work-in-progress hook.
673
+ const dispatch: Dispatch<A> = (queue.dispatch: any);
674
+ if (renderPhaseUpdates !== null) {
675
+ // Render phase updates are stored in a map of queue -> linked list
676
+ const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
677
+ if (firstRenderPhaseUpdate !== undefined) {
678
+ renderPhaseUpdates.delete(queue);
679
+ let newState = hook.memoizedState;
680
+ let update = firstRenderPhaseUpdate;
681
+ do {
682
+ // Process this render phase update. We don't have to check the
683
+ // priority because it will always be the same as the current
684
+ // render's.
685
+ const action = update.action;
686
+ newState = reducer(newState, action);
687
+ update = update.next;
688
+ } while (update !== null);
689
+
690
+ // Mark that the fiber performed work, but only if the new state is
691
+ // different from the current state.
692
+ if (!is(newState, hook.memoizedState)) {
693
+ markWorkInProgressReceivedUpdate();
694
+ }
695
+
696
+ hook.memoizedState = newState;
697
+ // Don't persist the state accumulated from the render phase updates to
698
+ // the base state unless the queue is empty.
699
+ // TODO: Not sure if this is the desired semantics, but it's what we
700
+ // do for gDSFP. I can't remember why.
701
+ if (hook.baseQueue === null) {
702
+ hook.baseState = newState;
703
+ }
704
+
705
+ queue.lastRenderedState = newState;
706
+
707
+ return [newState, dispatch];
708
+ }
709
+ }
710
+ return [hook.memoizedState, dispatch];
711
+ }
712
+
713
const current: Hook = (currentHook: any);
714
715
// The last rebase update that is NOT part of the base state.
@@ -795,60 +827,6 @@ function updateReducer<S, I, A>(
827
return [hook.memoizedState, dispatch];
828
}
829
798
-function rerenderReducer<S, I, A>(
799
- reducer: (S, A) => S,
800
- initialArg: I,
801
- init?: I => S,
802
-): [S, Dispatch<A>] {
803
- const hook = updateWorkInProgressHook();
804
- const queue = hook.queue;
805
- invariant(
806
- queue !== null,
807
- 'Should have a queue. This is likely a bug in React. Please file an issue.',
808
- );
809
-
810
- queue.lastRenderedReducer = reducer;
811
-
812
- // This is a re-render. Apply the new render phase updates to the previous
813
- // work-in-progress hook.
814
- const dispatch: Dispatch<A> = (queue.dispatch: any);
815
- const lastRenderPhaseUpdate = queue.pending;
816
- let newState = hook.memoizedState;
817
- if (lastRenderPhaseUpdate !== null) {
818
- // The queue doesn't persist past this render pass.
819
- queue.pending = null;
820
-
821
- const firstRenderPhaseUpdate = lastRenderPhaseUpdate.next;
822
- let update = firstRenderPhaseUpdate;
823
- do {
824
- // Process this render phase update. We don't have to check the
825
- // priority because it will always be the same as the current
826
- // render's.
827
- const action = update.action;
828
- newState = reducer(newState, action);
829
- update = update.next;
830
- } while (update !== firstRenderPhaseUpdate);
831
-
832
- // Mark that the fiber performed work, but only if the new state is
833
- // different from the current state.
834
- if (!is(newState, hook.memoizedState)) {
835
- markWorkInProgressReceivedUpdate();
836
- }
837
-
838
- hook.memoizedState = newState;
839
- // Don't persist the state accumulated from the render phase updates to
840
- // the base state unless the queue is empty.
841
- // TODO: Not sure if this is the desired semantics, but it's what we
842
- // do for gDSFP. I can't remember why.
843
- if (hook.baseQueue === null) {
844
- hook.baseState = newState;
845
- }
846
-
847
- queue.lastRenderedState = newState;
848
- }
849
- return [newState, dispatch];
850
-}
851
-
830
function mountState<S>(
831
initialState: (() => S) | S,
832
): [S, Dispatch<BasicStateAction<S>>] {
@@ -879,12 +857,6 @@ function updateState<S>(
857
return updateReducer(basicStateReducer, (initialState: any));
858
}
859
882
-function rerenderState<S>(
883
- initialState: (() => S) | S,
884
-): [S, Dispatch<BasicStateAction<S>>] {
885
- return rerenderReducer(basicStateReducer, (initialState: any));
886
-}
887
-
860
function pushEffect(tag, create, destroy, deps) {
861
const effect: Effect = {
862
tag,
@@ -1254,6 +1226,12 @@ function dispatchAction<S, A>(
1226
queue: UpdateQueue<S, A>,
1227
action: A,
1228
) {
1229
+ invariant(
1230
+ numberOfReRenders < RE_RENDER_LIMIT,
1231
+ 'Too many re-renders. React limits the number of renders to prevent ' +
1232
+ 'an infinite loop.',
1233
+ );
1234
+
1235
if (__DEV__) {
1236
if (typeof arguments[3] === 'function') {
1237
console.error(
@@ -1264,38 +1242,6 @@ function dispatchAction<S, A>(
1242
}
1243
}
1244
1267
- const currentTime = requestCurrentTimeForUpdate();
1268
- const suspenseConfig = requestCurrentSuspenseConfig();
1269
- const expirationTime = computeExpirationForFiber(
1270
- currentTime,
1271
- fiber,
1272
- suspenseConfig,
1273
- );
1274
-
1275
- const update: Update<S, A> = {
1276
- expirationTime,
1277
- suspenseConfig,
1278
- action,
1279
- eagerReducer: null,
1280
- eagerState: null,
1281
- next: (null: any),
1282
- };
1283
-
1284
- if (__DEV__) {
1285
- update.priority = getCurrentPriorityLevel();
1286
- }
1287
-
1288
- // Append the update to the end of the list.
1289
- const pending = queue.pending;
1290
- if (pending === null) {
1291
- // This is the first update. Create a circular list.
1292
- update.next = update;
1293
- } else {
1294
- update.next = pending.next;
1295
- pending.next = update;
1296
- }
1297
- queue.pending = update;
1298
-
1245
const alternate = fiber.alternate;
1246
if (
1247
fiber === currentlyRenderingFiber ||
@@ -1305,8 +1251,64 @@ function dispatchAction<S, A>(
1251
// queue -> linked list of updates. After this render pass, we'll restart
1252
// and apply the stashed updates on top of the work-in-progress hook.
1253
didScheduleRenderPhaseUpdate = true;
1308
- update.expirationTime = renderExpirationTime;
1254
+ const update: Update<S, A> = {
1255
+ expirationTime: renderExpirationTime,
1256
+ suspenseConfig: null,
1257
+ action,
1258
+ eagerReducer: null,
1259
+ eagerState: null,
1260
+ next: (null: any),
1261
+ };
1262
+ if (__DEV__) {
1263
+ update.priority = getCurrentPriorityLevel();
1264
+ }
1265
+ if (renderPhaseUpdates === null) {
1266
+ renderPhaseUpdates = new Map();
1267
+ }
1268
+ const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
1269
+ if (firstRenderPhaseUpdate === undefined) {
1270
+ renderPhaseUpdates.set(queue, update);
1271
+ } else {
1272
+ // Append the update to the end of the list.
1273
+ let lastRenderPhaseUpdate = firstRenderPhaseUpdate;
1274
+ while (lastRenderPhaseUpdate.next !== null) {
1275
+ lastRenderPhaseUpdate = lastRenderPhaseUpdate.next;
1276
+ }
1277
+ lastRenderPhaseUpdate.next = update;
1278
+ }
1279
} else {
1280
+ const currentTime = requestCurrentTimeForUpdate();
1281
+ const suspenseConfig = requestCurrentSuspenseConfig();
1282
+ const expirationTime = computeExpirationForFiber(
1283
+ currentTime,
1284
+ fiber,
1285
+ suspenseConfig,
1286
+ );
1287
+
1288
+ const update: Update<S, A> = {
1289
+ expirationTime,
1290
+ suspenseConfig,
1291
+ action,
1292
+ eagerReducer: null,
1293
+ eagerState: null,
1294
+ next: (null: any),
1295
+ };
1296
+
1297
+ if (__DEV__) {
1298
+ update.priority = getCurrentPriorityLevel();
1299
+ }
1300
+
1301
+ // Append the update to the end of the list.
1302
+ const pending = queue.pending;
1303
+ if (pending === null) {
1304
+ // This is the first update. Create a circular list.
1305
+ update.next = update;
1306
+ } else {
1307
+ update.next = pending.next;
1308
+ pending.next = update;
1309
+ }
1310
+ queue.pending = update;
1311
+
1312
if (
1313
fiber.expirationTime === NoWork &&
1314
(alternate === null || alternate.expirationTime === NoWork)
@@ -1411,31 +1413,11 @@ const HooksDispatcherOnUpdate: Dispatcher = {
1413
useTransition: updateTransition,
1414
};
1415
1414
-const HooksDispatcherOnRerender: Dispatcher = {
1415
- readContext,
1416
-
1417
- useCallback: updateCallback,
1418
- useContext: readContext,
1419
- useEffect: updateEffect,
1420
- useImperativeHandle: updateImperativeHandle,
1421
- useLayoutEffect: updateLayoutEffect,
1422
- useMemo: updateMemo,
1423
- useReducer: rerenderReducer,
1424
- useRef: updateRef,
1425
- useState: rerenderState,
1426
- useDebugValue: updateDebugValue,
1427
- useResponder: createResponderListener,
1428
- useDeferredValue: updateDeferredValue,
1429
- useTransition: updateTransition,
1430
-};
1431
-
1416
let HooksDispatcherOnMountInDEV: Dispatcher | null = null;
1417
let HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher | null = null;
1418
let HooksDispatcherOnUpdateInDEV: Dispatcher | null = null;
1435
-let HooksDispatcherOnRerenderInDEV: Dispatcher | null = null;
1419
let InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher | null = null;
1420
let InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher | null = null;
1438
-let InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher | null = null;
1421
1422
if (__DEV__) {
1423
const warnInvalidContextAccess = () => {
@@ -1812,123 +1794,6 @@ if (__DEV__) {
1794
},
1795
};
1796
1815
- HooksDispatcherOnRerenderInDEV = {
1816
- readContext<T>(
1817
- context: ReactContext<T>,
1818
- observedBits: void | number | boolean,
1819
- ): T {
1820
- return readContext(context, observedBits);
1821
- },
1822
-
1823
- useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
1824
- currentHookNameInDev = 'useCallback';
1825
- updateHookTypesDev();
1826
- return updateCallback(callback, deps);
1827
- },
1828
- useContext<T>(
1829
- context: ReactContext<T>,
1830
- observedBits: void | number | boolean,
1831
- ): T {
1832
- currentHookNameInDev = 'useContext';
1833
- updateHookTypesDev();
1834
- return readContext(context, observedBits);
1835
- },
1836
- useEffect(
1837
- create: () => (() => void) | void,
1838
- deps: Array<mixed> | void | null,
1839
- ): void {
1840
- currentHookNameInDev = 'useEffect';
1841
- updateHookTypesDev();
1842
- return updateEffect(create, deps);
1843
- },
1844
- useImperativeHandle<T>(
1845
- ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
1846
- create: () => T,
1847
- deps: Array<mixed> | void | null,
1848
- ): void {
1849
- currentHookNameInDev = 'useImperativeHandle';
1850
- updateHookTypesDev();
1851
- return updateImperativeHandle(ref, create, deps);
1852
- },
1853
- useLayoutEffect(
1854
- create: () => (() => void) | void,
1855
- deps: Array<mixed> | void | null,
1856
- ): void {
1857
- currentHookNameInDev = 'useLayoutEffect';
1858
- updateHookTypesDev();
1859
- return updateLayoutEffect(create, deps);
1860
- },
1861
- useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
1862
- currentHookNameInDev = 'useMemo';
1863
- updateHookTypesDev();
1864
- const prevDispatcher = ReactCurrentDispatcher.current;
1865
- ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnRerenderInDEV;
1866
- try {
1867
- return updateMemo(create, deps);
1868
- } finally {
1869
- ReactCurrentDispatcher.current = prevDispatcher;
1870
- }
1871
- },
1872
- useReducer<S, I, A>(
1873
- reducer: (S, A) => S,
1874
- initialArg: I,
1875
- init?: I => S,
1876
- ): [S, Dispatch<A>] {
1877
- currentHookNameInDev = 'useReducer';
1878
- updateHookTypesDev();
1879
- const prevDispatcher = ReactCurrentDispatcher.current;
1880
- ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnRerenderInDEV;
1881
- try {
1882
- return rerenderReducer(reducer, initialArg, init);
1883
- } finally {
1884
- ReactCurrentDispatcher.current = prevDispatcher;
1885
- }
1886
- },
1887
- useRef<T>(initialValue: T): {current: T} {
1888
- currentHookNameInDev = 'useRef';
1889
- updateHookTypesDev();
1890
- return updateRef(initialValue);
1891
- },
1892
- useState<S>(
1893
- initialState: (() => S) | S,
1894
- ): [S, Dispatch<BasicStateAction<S>>] {
1895
- currentHookNameInDev = 'useState';
1896
- updateHookTypesDev();
1897
- const prevDispatcher = ReactCurrentDispatcher.current;
1898
- ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnRerenderInDEV;
1899
- try {
1900
- return rerenderState(initialState);
1901
- } finally {
1902
- ReactCurrentDispatcher.current = prevDispatcher;
1903
- }
1904
- },
1905
- useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
1906
- currentHookNameInDev = 'useDebugValue';
1907
- updateHookTypesDev();
1908
- return updateDebugValue(value, formatterFn);
1909
- },
1910
- useResponder<E, C>(
1911
- responder: ReactEventResponder<E, C>,
1912
- props,
1913
- ): ReactEventResponderListener<E, C> {
1914
- currentHookNameInDev = 'useResponder';
1915
- updateHookTypesDev();
1916
- return createResponderListener(responder, props);
1917
- },
1918
- useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T {
1919
- currentHookNameInDev = 'useDeferredValue';
1920
- updateHookTypesDev();
1921
- return updateDeferredValue(value, config);
1922
- },
1923
- useTransition(
1924
- config: SuspenseConfig | void | null,
1925
- ): [(() => void) => void, boolean] {
1926
- currentHookNameInDev = 'useTransition';
1927
- updateHookTypesDev();
1928
- return updateTransition(config);
1929
- },
1930
- };
1931
-
1797
InvalidNestedHooksDispatcherOnMountInDEV = {
1798
readContext<T>(
1799
context: ReactContext<T>,
@@ -2190,135 +2055,4 @@ if (__DEV__) {
2055
return updateTransition(config);
2056
},
2057
};
2193
-
2194
- InvalidNestedHooksDispatcherOnRerenderInDEV = {
2195
- readContext<T>(
2196
- context: ReactContext<T>,
2197
- observedBits: void | number | boolean,
2198
- ): T {
2199
- warnInvalidContextAccess();
2200
- return readContext(context, observedBits);
2201
- },
2202
-
2203
- useCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
2204
- currentHookNameInDev = 'useCallback';
2205
- warnInvalidHookAccess();
2206
- updateHookTypesDev();
2207
- return updateCallback(callback, deps);
2208
- },
2209
- useContext<T>(
2210
- context: ReactContext<T>,
2211
- observedBits: void | number | boolean,
2212
- ): T {
2213
- currentHookNameInDev = 'useContext';
2214
- warnInvalidHookAccess();
2215
- updateHookTypesDev();
2216
- return readContext(context, observedBits);
2217
- },
2218
- useEffect(
2219
- create: () => (() => void) | void,
2220
- deps: Array<mixed> | void | null,
2221
- ): void {
2222
- currentHookNameInDev = 'useEffect';
2223
- warnInvalidHookAccess();
2224
- updateHookTypesDev();
2225
- return updateEffect(create, deps);
2226
- },
2227
- useImperativeHandle<T>(
2228
- ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
2229
- create: () => T,
2230
- deps: Array<mixed> | void | null,
2231
- ): void {
2232
- currentHookNameInDev = 'useImperativeHandle';
2233
- warnInvalidHookAccess();
2234
- updateHookTypesDev();
2235
- return updateImperativeHandle(ref, create, deps);
2236
- },
2237
- useLayoutEffect(
2238
- create: () => (() => void) | void,
2239
- deps: Array<mixed> | void | null,
2240
- ): void {
2241
- currentHookNameInDev = 'useLayoutEffect';
2242
- warnInvalidHookAccess();
2243
- updateHookTypesDev();
2244
- return updateLayoutEffect(create, deps);
2245
- },
2246
- useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T {
2247
- currentHookNameInDev = 'useMemo';
2248
- warnInvalidHookAccess();
2249
- updateHookTypesDev();
2250
- const prevDispatcher = ReactCurrentDispatcher.current;
2251
- ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
2252
- try {
2253
- return updateMemo(create, deps);
2254
- } finally {
2255
- ReactCurrentDispatcher.current = prevDispatcher;
2256
- }
2257
- },
2258
- useReducer<S, I, A>(
2259
- reducer: (S, A) => S,
2260
- initialArg: I,
2261
- init?: I => S,
2262
- ): [S, Dispatch<A>] {
2263
- currentHookNameInDev = 'useReducer';
2264
- warnInvalidHookAccess();
2265
- updateHookTypesDev();
2266
- const prevDispatcher = ReactCurrentDispatcher.current;
2267
- ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
2268
- try {
2269
- return rerenderReducer(reducer, initialArg, init);
2270
- } finally {
2271
- ReactCurrentDispatcher.current = prevDispatcher;
2272
- }
2273
- },
2274
- useRef<T>(initialValue: T): {current: T} {
2275
- currentHookNameInDev = 'useRef';
2276
- warnInvalidHookAccess();
2277
- updateHookTypesDev();
2278
- return updateRef(initialValue);
2279
- },
2280
- useState<S>(
2281
- initialState: (() => S) | S,
2282
- ): [S, Dispatch<BasicStateAction<S>>] {
2283
- currentHookNameInDev = 'useState';
2284
- warnInvalidHookAccess();
2285
- updateHookTypesDev();
2286
- const prevDispatcher = ReactCurrentDispatcher.current;
2287
- ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
2288
- try {
2289
- return rerenderState(initialState);
2290
- } finally {
2291
- ReactCurrentDispatcher.current = prevDispatcher;
2292
- }
2293
- },
2294
- useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {
2295
- currentHookNameInDev = 'useDebugValue';
2296
- warnInvalidHookAccess();
2297
- updateHookTypesDev();
2298
- return updateDebugValue(value, formatterFn);
2299
- },
2300
- useResponder<E, C>(
2301
- responder: ReactEventResponder<E, C>,
2302
- props,
2303
- ): ReactEventResponderListener<E, C> {
2304
- currentHookNameInDev = 'useResponder';
2305
- warnInvalidHookAccess();
2306
- updateHookTypesDev();
2307
- return createResponderListener(responder, props);
2308
- },
2309
- useDeferredValue<T>(value: T, config: TimeoutConfig | void | null): T {
2310
- currentHookNameInDev = 'useDeferredValue';
2311
- warnInvalidHookAccess();
2312
- updateHookTypesDev();
2313
- return updateDeferredValue(value, config);
2314
- },
2315
- useTransition(
2316
- config: SuspenseConfig | void | null,
2317
- ): [(() => void) => void, boolean] {
2318
- currentHookNameInDev = 'useTransition';
2319
- warnInvalidHookAccess();
2320
- updateHookTypesDev();
2321
- return updateTransition(config);
2322
- },
2323
- };
2058
}
packages/react-reconciler/src/ReactFiberWorkLoop.js
+3
-3
@@ -140,7 +140,7 @@ import {
140
} from './ReactFiberCommitWork';
141
import {enqueueUpdate} from './ReactUpdateQueue';
142
import {resetContextDependencies} from './ReactFiberNewContext';
143
-import {resetHooksAfterThrow, ContextOnlyDispatcher} from './ReactFiberHooks';
143
+import {resetHooks, ContextOnlyDispatcher} from './ReactFiberHooks';
144
import {createCapturedValue} from './ReactCapturedValue';
145
146
import {
@@ -1280,7 +1280,7 @@ function handleError(root, thrownValue) {
1280
try {
1281
// Reset module-level state that was set during the render phase.
1282
resetContextDependencies();
1283
- resetHooksAfterThrow();
1283
+ resetHooks();
1284
resetCurrentDebugFiberInDEV();
1285
1286
if (workInProgress === null || workInProgress.return === null) {
@@ -2634,7 +2634,7 @@ if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
2634
// Keep this code in sync with handleError; any changes here must have
2635
// corresponding changes there.
2636
resetContextDependencies();
2637
- resetHooksAfterThrow();
2637
+ resetHooks();
2638
// Don't reset current debug fiber, since we're about to work on the
2639
// same fiber again.
2640
packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.internal.js
-44
@@ -542,50 +542,6 @@ describe('ReactHooksWithNoopRenderer', () => {
542
]);
543
expect(ReactNoop.getChildren()).toEqual([span(22)]);
544
});
545
-
546
- it('discards render phase updates if something suspends', () => {
547
- const thenable = {then() {}};
548
- function Foo({signal}) {
549
- return (
550
- <Suspense fallback="Loading...">
551
- <Bar signal={signal} />
552
- </Suspense>
553
- );
554
- }
555
-
556
- function Bar({signal: newSignal}) {
557
- let [counter, setCounter] = useState(0);
558
- let [signal, setSignal] = useState(true);
559
-
560
- // Increment a counter every time the signal changes
561
- if (signal !== newSignal) {
562
- setCounter(c => c + 1);
563
- setSignal(newSignal);
564
- if (counter === 0) {
565
- // We're suspending during a render that includes render phase
566
- // updates. Those updates should not persist to the next render.
567
- Scheduler.unstable_yieldValue('Suspend!');
568
- throw thenable;
569
- }
570
- }
571
-
572
- return <Text text={counter} />;
573
- }
574
-
575
- const root = ReactNoop.createRoot();
576
- root.render(<Foo signal={true} />);
577
-
578
- expect(Scheduler).toFlushAndYield([0]);
579
- expect(root).toMatchRenderedOutput(<span prop={0} />);
580
-
581
- root.render(<Foo signal={false} />);
582
- expect(Scheduler).toFlushAndYield(['Suspend!']);
583
- expect(root).toMatchRenderedOutput(<span prop={0} />);
584
-
585
- // Rendering again should suspend again.
586
- root.render(<Foo signal={false} />);
587
- expect(Scheduler).toFlushAndYield(['Suspend!']);
588
- });
545
});
546
547
describe('useReducer', () => {