useSyncExternalStore — the external-store bridge

[check: …]
📖 guide (.md) ← react deep dive
📖 Pair this live React playground with the companion guide (.md) — this page is the rendered ground truth. ↗ Context is React-internal state — useSyncExternalStore is for external state. Compare with use_context (the broadcast model for in-tree state).

external state, safely synced

useState/useReducer own state inside React. useSyncExternalStore is for the world outside React — a custom store, window.innerWidth, navigator.onLine, Zustand, Redux, a WebSocket. You hand it a subscribe function and a getSnapshot function; it reads the snapshot, subscribes, and re-renders the moment the store notifies. Crucially, during one concurrent render pass every component reads the same snapshot — so the UI can never tear (show two inconsistent views of one store).

pieceroleanalogy
subscribe(cb) register a listener; return an unsubscribe function hand the store your phone number — and a way to hang up
getSnapshot() return the current snapshot — must be immutable & cached the photo of state taken right now
getServerSnapshot() initial snapshot for SSR / hydration (optional, 3rd arg) the photo taken before the page is interactive
snapshot stability same data → same reference (Object.is) break this → infinite re-render loop

1 · the store you write (edit me)

2 · Babel compiles JSX → element tree

<App/> becomes React.createElement(App). Each store.setState(...) fires every listener → React reads store.getState() again → re-renders all subscribers with one consistent snapshot.

// (hit "compile & render" to see Babel's output)

3 · live React (the subscribe/getSnapshot contract, proven)

Click "+1" — it calls store.setState(), which notifies the listeners React registered. StoreCounter and Mirror are independent components reading the same store; both update in lockstep. The gold-check does this automatically: assert Count: 0, click +1 three times, assert 1 → 2 → 3, then confirm the mirror is in sync.

counter: · mirror: · gold: 0 → click+1×3 → 1,2,3 + mirror in sync

intent → pattern

intentpatternwhy
subscribe to a custom store useSyncExternalStore(store.subscribe, store.getState) React registers a listener; re-renders when the store notifies
subscribe to a browser API useSyncExternalStore(subResize, () => window.innerWidth) reactive innerWidth/onLine/media-query without manual useEffect
cache the snapshot store a stable ref; return it unchanged when data is the same if getSnapshot returns a new ref each call → infinite loop
server-side rendering pass getServerSnapshot as the 3rd argument consistent first paint before hydration; avoids hydration mismatch
mutate the store store.setState(s => ({...s, count: s.count+1})) + notify every subscriber reads the new immutable snapshot in one render pass