Hydration Proof

Search documentation

Find a page or section

Require useSyncExternalStore to have a getServerSnapshot that returns the same value on the server and during hydration.

useSyncExternalStore needs a getServerSnapshot, its third argument, to render on the server and to hydrate, and that function must return the same value in both places. require-stable-server-snapshot reports a missing getServerSnapshot, and one that reads the browser, the clock or random values.

Rulehydration-proof/require-stable-server-snapshot
What it reportsRequire useSyncExternalStore to have a getServerSnapshot that returns the same value on the server and during hydration
recommended / nextError
strictError
Server ComponentsSkipped with the next preset (they never hydrate)
SuggestionsNo
Optionsnone

What it reports

  • useSyncExternalStore(subscribe, getSnapshot) without a third argument (or with undefined), including React.useSyncExternalStore and the use-sync-external-store/shim export.
  • Inside getServerSnapshot (an inline function, or a function declared in the same file and passed by name): any read of a browser-only global, localStorage/sessionStorage, matchMedia, the clock (Date.now(), new Date(), ...), random values (Math.random(), ...) and environment checks (typeof window).

Functions nested inside getServerSnapshot and functions imported from other files are not inspected.

Why getServerSnapshot must be stable

During server rendering React calls getServerSnapshot; without it, rendering the component on the server fails and React renders it again in the browser (Missing getServerSnapshot is that error). During hydration React calls getServerSnapshot again, in the browser, and expects the value the server used. Only after hydration does it switch to getSnapshot:

server:     getServerSnapshot() → typeof window === 'undefined' → 'light'
hydration:  getServerSnapshot() → typeof window === 'undefined' → 'dark'   (mismatch)

So getServerSnapshot must be a pure function of data both sides have.

Incorrect

function useOnline() {
  return useSyncExternalStore(subscribe, () => navigator.onLine);
}
 
function useTheme() {
  return useSyncExternalStore(
    subscribeToStorage,
    () => localStorage.getItem("theme"),
    () => localStorage.getItem("theme") ?? "light"
  );
}
 
const getWidth = () => window.innerWidth;
 
function useWidth() {
  return useSyncExternalStore(subscribeToResize, getWidth, getWidth);
}

Correct

function useOnline() {
  return useSyncExternalStore(
    subscribe,
    () => navigator.onLine,
    () => true
  );
}
 
function useTheme(serverTheme) {
  return useSyncExternalStore(
    subscribeToStorage,
    () => localStorage.getItem("theme") ?? serverTheme,
    () => serverTheme // read from a cookie on the server and passed as a prop
  );
}
 
const getWidth = () => window.innerWidth;
const getServerWidth = () => 1024;
 
function useWidth() {
  return useSyncExternalStore(subscribeToResize, getWidth, getServerWidth);
}

Options

This rule has no options.

Messages

What ESLint prints for this rule, word for word:

  • useSyncExternalStore has no getServerSnapshot. React needs it to render on the server and to hydrate: without it the server render errors and the component is rendered again in the browser. Pass a third argument that returns the value the server renders.
  • getServerSnapshot reads <read>. It must return the same value on the server and while hydrating, so it cannot depend on the browser, the clock or randomness. Return a constant or data passed from the server.

When not to use it

When the store is only used in components that are never server-rendered.