UseSyncExternalStore lets a component read data that lives outside React without risking the visual glitches that concurrent rendering can introduce. It replaces the old pattern of pulling external values into local state with useEffect, a pattern that can cause “tearing” – parts of the UI showing different values during the same render.
Why the old pattern breaks under concurrency
Before React 18, the common recipe was:
const [value, setValue] = useState(initial);
useEffect(() => {
const unsubscribe = externalSource.subscribe(v => setValue(v));
return unsubscribe;
}, []);
The effect runs after the component has rendered. When React’s concurrent renderer decides to pause, abort, or replay a render, the effect may fire at a different point than the UI that reads the state. The result is a mismatch: one component may have read the old value, another the new one, and the screen briefly shows inconsistent data. This “tearing” is harmless in a purely synchronous world but becomes a visible bug when React renders multiple versions of the UI in parallel.
How useSyncExternalStore solves the problem
The hook requires two callbacks:
- subscribe – registers a listener and returns a function that removes it.
- getSnapshot – returns the current value of the external source.
React calls subscribe when the component mounts and disposes it on unmount. During every render it invokes getSnapshot and guarantees that all components using the same external store read the exact same snapshot. Because the read happens inside the render phase, the renderer can coordinate updates across the whole tree, eliminating tearing.
Libraries such as Zustand and Redux already wrap their stores with this hook, which is why they work out of the box with React 18’s concurrent features.
Rules that keep the hook from misbehaving
- Return only what the component needs – a large object forces React to compare deep structures on every update. A narrow snapshot keeps change detection cheap.
- Preserve referential identity – if the external value has not changed, getSnapshot must return the same object reference. Returning a new object each call triggers an infinite render loop because React sees a change on every render.
- Limit use to external sources – local component state belongs in useState or useReducer. Using the external-store hook for purely internal values adds unnecessary indirection.
When to reach for useSyncExternalStore
- Wrapping browser APIs (window size, network-online status, media queries).
- Connecting a plain JavaScript store that does not know about React.
- Replacing prop-drilling with a global read-only source that many components subscribe to.
The counter-argument
The useState + useEffect pattern still works for simple, infrequently changing data, especially when an application does not enable concurrent features. It is a lower-level approach that some developers find easier to reason about because the subscription logic lives explicitly in the effect. However, the trade-off is the risk of tearing once concurrent rendering is turned on. For projects that already use React 18’s new capabilities, the extra safety of useSyncExternalStore outweighs the minimal added boilerplate.
What to watch next
- Verify that your store’s getSnapshot returns a stable reference; a quick console.log of the reference during development can expose accidental object recreation.
- Keep the subscription function lightweight – heavy work should happen inside the external source, not in the callback passed to React.
- Test components under React’s concurrent mode (e.g., using
actwithReactDOM.createRoot) to confirm that no tearing appears.
A live demo shows the hook in action, syncing a simple counter stored outside React and updating multiple components without visual glitches. The demo is available at https://usesyncexternalstore.vercel.app/. The source code lives on GitHub, and a longer write-up explains the implementation details.
Takeaway: In a world where React can render parts of the UI out of order, useSyncExternalStore is the reliable bridge between external data and component rendering. It preserves visual consistency, scales to global stores, and is already baked into popular state-management libraries. Adopt it wherever you need to read non-React state, and keep snapshots small and stable to reap its full benefits.
