You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
useLocationState: hydration mismatch when the reader hydrates inside a Suspense boundary #992
useLocationState reads history.state in its useState initializer once <html> has data-hydrated (src/browser/react/location-state.ts:26-31, :76-82). The root sets that attribute from an effect after it hydrates (src/browser/rsc-router.tsx:699).
A reader inside a Suspense boundary that hydrates after that effect runs the initializer during its own hydration. hasHydrated() is already true there, so it reads the stored value and renders it. The server rendered undefined, so React reports a hydration mismatch and client-renders the boundary.
The deferred first read (initialReadDeferredRef) handles readers that hydrate with the root. It doesn't cover readers that hydrate later, which in an RSC app is most per-page UI under a <Suspense>.
Soft-navigate to the page with router.push(url, { state: [GridState({ count: 3 })] }).
Hard-reload the page. The history entry keeps its state.
The server renders 0. The root hydrates and sets data-hydrated. The boundary then hydrates, the initializer reads 3, and React reports "Hydration failed because the server rendered text didn't match the client".
Expected
The first client render of any hydrating reader matches the server (undefined), and the reader updates to the stored value right after, as it already does for readers that hydrate with the root.
Suggested fix
Read through useSyncExternalStore(subscribe, getSnapshot, () => undefined). React uses the server snapshot for whichever render hydrates the component, wherever it sits, then re-renders with the client snapshot. The flash variant's read-once / clear-after-paint logic would stay in the effect. getSnapshot needs a stable reference for an unchanged entry, which history.state[key] already gives.
Workaround
The reader returns undefined while it is hydrating, using a local useSyncExternalStore(noop, () => false, () => true) flag, and the stored value after that.
Activity
fix(router): useLocationState hydrates as undefined inside Suspense
test(router): e2e for useLocationState inside a late-hydrating Suspen…
feat(testing): renderRoute can hydrate (#992)
refactor(testing): renderRoute hydrate models the root marker; shared…
fix(router): a document served from a PPR shell hydrates with the han…