Description
React version: #22347
Steps To Reproduce
- Read store (using
useSyncExternalStore
) inside a Suspense boundary where no component suspended - Read store (using
useSyncExternalStore
) outside of any Suspense boundary - Update store outside of any Suspense boundary in an effect (
useEffect
)
<Store>
<Suspense fallback={null}>
<Demo>inside suspense has hydration mismatch</Demo>
{/* When the fallback is actually used on the server, we don't get a hydration mismatch */}
{/* <Suspender /> */}
</Suspense>
<Demo>outside suspense has no hydration mismatch</Demo>
<UpdatesStore />
</Store>
);
Link to code example: https://codesandbox.io/s/react-18-updating-store-in-an-effect-during-mount-causes-hydration-mismatch-uses-m6lwm?file=/src/index.js
The current behavior
<Demo />
inside the Suspense boundary causes a hydration mismatch since it's hydrated with the value set during useEffect
.
The expected behavior
No hydration mismatch.
Repro explainer
The repro is based on reduxjs/react-redux#1794 which is based on a usage from the mui.com docs.
The behavior this repro is implementing is reading a value from window.localStorage
(e.g. settings) with a fallback on the server.
The store
is a Redux store that is the same on Server and Client.
Reading from the store
is implemented like so:
const ReduxStoreContext = createContext();
function useValueRedux() {
const store = useContext(ReduxStoreContext);
const selector = useCallback(() => store.getState().codeVariant, [store]);
// The store is equivalent on Client and Server so we can just re-use `getSnapshot` for `getServerSnapshot`
return useSyncExternalStore(store.subscribe, selector, selector);
}
The repro contains an implementation that uses React Context for the store which works as expected i.e. no hydration mismatches.
Context
I recently stumbled over
(In general, updates inside a passive effect are not encouraged.)
-- #22277
which makes it sound like this behavior is expected because the update is inside a passive effect. Though it's unclear what is meant by "not encourage". How would I render a default value on the Server and populate it with the actually desired value on the Client?