React 18 added useSyncExternalStore, a hook that lets components read data from any source that lives outside the React tree—browser APIs, WebSockets, or third-party stores—without the visual glitches that plagued the old pattern of mirroring that data with useState and useEffect. If you’ve ever seen a UI flicker because part of it showed an old window size while another part already reflected the new one, this hook is the fix you’ve been waiting for.
Why external state needs a new hook
React components have always been able to subscribe to things that exist beyond their own scope: the window object, a Redux store, a WebSocket connection. The conventional approach was to set up a subscription inside a useEffect, push incoming values into local state via setState, and let React re-render. That works fine when React renders synchronously, but React 18 introduced concurrent rendering, where a component may be rendered multiple times before the browser actually paints. In that mode the “mirror-into-local-state” pattern can produce tearing—different parts of the UI reading different snapshots of the same external value during a single render cycle.
useSyncExternalStore was built to prevent tearing. It asks the external source for a snapshot (the current value) and for a subscribe function (how to be notified of changes). React calls subscribe when the component mounts and automatically unsubscribes on unmount. Whenever the snapshot returned by getSnapshot changes, React schedules a render that reads the same snapshot for the whole component tree, guaranteeing consistency.
The two required functions
| Function | What it does |
|---|---|
| subscribe | Receives a callback that must be called whenever the external value changes. It returns an unsubscribe function that React will call on cleanup. |
| getSnapshot | Returns the current value from the external source. It must be referentially stable—the same function object on every render—so React can compare snapshots correctly. |
A minimal implementation for the browser’s online status looks like this:
function useOnlineStatus() {
return useSyncExternalStore(
(onChange) => {
window.addEventListener('online', onChange);
window.addEventListener('offline', onChange);
return () => {
window.removeEventListener('online', onChange);
window.removeEventListener('offline', onChange);
};
},
() => navigator.onLine
);
}
When any component calls useOnlineStatus(), React will re-render it only when navigator.onLine actually flips, and every render will see the same value.
Rules that keep the hook from blowing up
Return the smallest piece of data a component needs.
If the external store holds a large object but a component only cares about one field, return just that field. Larger returns cause unnecessary renders.Memoise
getSnapshot.
If you recreate the function on every render, React treats it as a new source and may loop forever. Wrap it inuseCallbackor define it outside the component.Avoid returning fresh objects each call.
Returning a new object (e.g.,{ count: store.getCount() }) creates a new reference on every render, making React think the snapshot changed and triggering an infinite render loop. Use primitives, strings, numbers, or memoised objects.Don’t use it for internal component state.
useStateis still the right tool for data that lives only inside the component.useSyncExternalStoreadds overhead that isn’t needed for local state.
When to reach for it
- Browser APIs – window size, media queries,
navigator.onLine, battery status. - Framework-agnostic stores – Zustand, Redux, MobX, or any custom store that exposes a subscription API.
- Mutable sources that update outside React – WebSocket messages, IndexedDB change events, Service Worker notifications.
If your data source already lives inside React (e.g., a parent component’s state), stick with useState or context.
What the community is saying
Early adopters report that useSyncExternalStore eliminates the flicker they saw when resizing windows in a concurrent-rendering setup. Some libraries have already switched their internal hooks to this API, promising more predictable behavior across React versions. The trade-off is a slightly higher mental load: developers must think about stability of the two functions and avoid returning new objects each call.
What to watch next
Die kommenden React-Releases könnten die Spezifikationen rund um useSyncExternalStore verschärfen und möglicherweise integrierte Helfer für gängige Browser-APIs hinzufügen. Behalten Sie den offiziellen React-Blog im Auge, um über etwaige Abkündigungen des alten useEffect-Mirroring-Musters informiert zu bleiben. In der Zwischenzeit ist der Hook stabil und Teil der öffentlichen API, sodass Sie bestehenden Code für externen State sicher refactoren können.
Kurzer Testlauf
Eine Live-Demo, die die Synchronisierung der Fenstergröße, die Online/Offline-Erkennung und einen einfachen Zustand-Store zeigt, finden Sie unter https://usesyncexternalstore.vercel.app/. Der Quellcode, inklusive Kommentaren, ist unter https://github.com/dev48v/usesyncexternalstore verfügbar.
Fazit: useSyncExternalStore bietet React eine zuverlässige Brücke zu allen veränderlichen Daten, die außerhalb seines Render-Zyklus liegen, wodurch Tearing und unnötige Renders verhindert werden. Wenden Sie ihn auf externe Quellen an, halten Sie den Snapshot minimal und stabil und überlassen Sie React die Schwerstarbeit bei der Konsistenz.
