Skip to content

Commit 1920123

Browse files
committed
Split out rerenderSyncExternalStore
I think this also fixes a bug where updateStoreInstance would not be pushed on initial mount rerender.
1 parent cd4a706 commit 1920123

File tree

1 file changed

+92
-7
lines changed

1 file changed

+92
-7
lines changed

packages/react-reconciler/src/ReactFiberHooks.js

Lines changed: 92 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1787,7 +1787,7 @@ function mountSyncExternalStore<T>(
17871787
return nextSnapshot;
17881788
}
17891789

1790-
function updateSyncExternalStore<T>(
1790+
function rerenderSyncExternalStore<T>(
17911791
subscribe: (() => void) => () => void,
17921792
getSnapshot: () => T,
17931793
getServerSnapshot?: () => T,
@@ -1822,7 +1822,83 @@ function updateSyncExternalStore<T>(
18221822
}
18231823
}
18241824
}
1825-
const prevSnapshot = (currentHook || hook).memoizedState;
1825+
let snapshotChanged = true;
1826+
if (currentHook !== null) {
1827+
const prevSnapshot = currentHook.memoizedState;
1828+
snapshotChanged = !is(prevSnapshot, nextSnapshot);
1829+
if (snapshotChanged) {
1830+
hook.memoizedState = nextSnapshot;
1831+
markWorkInProgressReceivedUpdate();
1832+
}
1833+
}
1834+
const inst = hook.queue;
1835+
1836+
updateEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [
1837+
subscribe,
1838+
]);
1839+
1840+
// Whenever getSnapshot or subscribe changes, we need to check in the
1841+
// commit phase if there was an interleaved mutation. In concurrent mode
1842+
// this can happen all the time, but even in synchronous mode, an earlier
1843+
// effect may have mutated the store.
1844+
if (
1845+
inst.getSnapshot !== getSnapshot ||
1846+
snapshotChanged ||
1847+
// Check if the subscribe function changed. We can save some memory by
1848+
// checking whether we scheduled a subscription effect above.
1849+
(workInProgressHook !== null &&
1850+
workInProgressHook.memoizedState.tag & HookHasEffect)
1851+
) {
1852+
fiber.flags |= PassiveEffect;
1853+
pushEffect(
1854+
HookHasEffect | HookPassive,
1855+
updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot),
1856+
createEffectInstance(),
1857+
null,
1858+
);
1859+
1860+
// Unless we're rendering a blocking lane, schedule a consistency check.
1861+
// Right before committing, we will walk the tree and check if any of the
1862+
// stores were mutated.
1863+
const root: FiberRoot | null = getWorkInProgressRoot();
1864+
1865+
if (root === null) {
1866+
throw new Error(
1867+
'Expected a work-in-progress root. This is a bug in React. Please file an issue.',
1868+
);
1869+
}
1870+
1871+
if (!isHydrating && !includesBlockingLane(root, renderLanes)) {
1872+
pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot);
1873+
}
1874+
}
1875+
1876+
return nextSnapshot;
1877+
}
1878+
1879+
function updateSyncExternalStore<T>(
1880+
subscribe: (() => void) => () => void,
1881+
getSnapshot: () => T,
1882+
getServerSnapshot?: () => T,
1883+
): T {
1884+
const fiber = currentlyRenderingFiber;
1885+
const hook = updateWorkInProgressHook();
1886+
// Read the current snapshot from the store on every render. This breaks the
1887+
// normal rules of React, and only works because store updates are
1888+
// always synchronous.
1889+
const nextSnapshot = getSnapshot();
1890+
if (__DEV__) {
1891+
if (!didWarnUncachedGetSnapshot) {
1892+
const cachedSnapshot = getSnapshot();
1893+
if (!is(nextSnapshot, cachedSnapshot)) {
1894+
console.error(
1895+
'The result of getSnapshot should be cached to avoid an infinite loop',
1896+
);
1897+
didWarnUncachedGetSnapshot = true;
1898+
}
1899+
}
1900+
}
1901+
const prevSnapshot = ((currentHook: any): Hook).memoizedState;
18261902
const snapshotChanged = !is(prevSnapshot, nextSnapshot);
18271903
if (snapshotChanged) {
18281904
hook.memoizedState = nextSnapshot;
@@ -1865,7 +1941,7 @@ function updateSyncExternalStore<T>(
18651941
);
18661942
}
18671943

1868-
if (!isHydrating && !includesBlockingLane(root, renderLanes)) {
1944+
if (!includesBlockingLane(root, renderLanes)) {
18691945
pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot);
18701946
}
18711947
}
@@ -2228,7 +2304,8 @@ function updateEffectImpl(
22282304
const effect: Effect = hook.memoizedState;
22292305
const inst = effect.inst;
22302306

2231-
// currentHook is null when rerendering after a render phase state update.
2307+
// currentHook is null on initial mount when rerendering after a render phase
2308+
// state update or for strict mode.
22322309
if (currentHook !== null) {
22332310
if (nextDeps !== null) {
22342311
const prevEffect: Effect = currentHook.memoizedState;
@@ -3315,7 +3392,7 @@ const HooksDispatcherOnRerender: Dispatcher = {
33153392
useDeferredValue: rerenderDeferredValue,
33163393
useTransition: rerenderTransition,
33173394
useMutableSource: updateMutableSource,
3318-
useSyncExternalStore: updateSyncExternalStore,
3395+
useSyncExternalStore: rerenderSyncExternalStore,
33193396
useId: updateId,
33203397
};
33213398
if (enableCache) {
@@ -4002,7 +4079,11 @@ if (__DEV__) {
40024079
): T {
40034080
currentHookNameInDev = 'useSyncExternalStore';
40044081
updateHookTypesDev();
4005-
return updateSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
4082+
return rerenderSyncExternalStore(
4083+
subscribe,
4084+
getSnapshot,
4085+
getServerSnapshot,
4086+
);
40064087
},
40074088
useId(): string {
40084089
currentHookNameInDev = 'useId';
@@ -4583,7 +4664,11 @@ if (__DEV__) {
45834664
currentHookNameInDev = 'useSyncExternalStore';
45844665
warnInvalidHookAccess();
45854666
updateHookTypesDev();
4586-
return updateSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
4667+
return rerenderSyncExternalStore(
4668+
subscribe,
4669+
getSnapshot,
4670+
getServerSnapshot,
4671+
);
45874672
},
45884673
useId(): string {
45894674
currentHookNameInDev = 'useId';

0 commit comments

Comments
 (0)