useSyncExternalStore: Concurrent Tearing Prevention in External Stores
- Last updated
- Prerequisites:
- React Concurrent Mode & Time Slicing
- Custom event listeners / Redux stores
- react
- useSyncExternalStore
- concurrent
- tearing
- state-management
- redux
Read at your depth
The practical view
`useSyncExternalStore` is the official hook for subscribing to non-React state stores (like Redux, Zustand, browser APIs like `window.navigator.onLine`, or custom event emitters). `const state = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)`. It replaces `useEffect` + `useState` store subscriptions and guarantees that components never display inconsistent or torn state during concurrent rendering.
The same idea in other frameworks
`useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)`RxJS `shareReplay(1)` combined with synchronous signal reads.Vue `shallowReadonly` reactive state tracking external stores.Svelte readable store contract (`subscribe()` with synchronous emission).Legacy vs modern
useEffect Store Subscription (Tearing Risk) vs useSyncExternalStore
Subscribing via `useEffect` suffers from race conditions and concurrent tearing; `useSyncExternalStore` ensures atomic consistency.
useEffect(() => store.subscribe(setValue), []); // ❌ Vulnerable to tearing in Concurrent ModeuseSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); // ✅ NEW: Tearing-proofInterview gotchas
Context
Interviewers test your knowledge of reference equality checks and infinite render loops.
The mechanical answer
React compares the return value of `getSnapshot()` using `Object.is`. If `getSnapshot` creates and returns a *new object or array reference on every call* (e.g. `() => ({ isOnline: navigator.onLine })`), React will believe the external store mutated on every single check, causing an **infinite re-render loop** and throwing a maximum update depth exceeded error.
Trap
Never return inline object literals `() => ({ ... })` from `getSnapshot`. Always return primitives or cached, immutable object references.
Context
Checks understanding of Server-Side Rendering (SSR) hydration contracts.
The mechanical answer
`getServerSnapshot` is required when using Server-Side Rendering (SSR). During server generation and initial client hydration, React executes `getServerSnapshot` to ensure the rendered markup matches between server and client, preventing hydration mismatch errors for browser-specific globals like `window` or `navigator`.
Trap
If `getServerSnapshot` is omitted in an SSR app, calling `useSyncExternalStore` on the server will throw an error.