useSyncExternalStore
Most developers never touch it directly — but almost every modern state management library depends on it.
If you’ve ever wondered:
- how Redux, Zustand, or proxy-state libraries safely integrate with React
- or how to properly sync React with things like localStorage
— this hook is the missing piece.
In this article, we’ll cover:
- what
useSyncExternalStoreis - why it exists
- how to use it to build a persistent localStorage store
What is useSyncExternalStore?
useSyncExternalStore is a React hook designed to let React safely subscribe to external state sources.
External state means any state that does not live inside React.
Examples:
localStorage- global objects
- Redux / Zustand stores
- browser APIs
- WebSocket data sources
It allows React to:
- subscribe to updates
- read the current value
- stay consistent with Concurrent Rendering
This hook is the official low-level API for integrating external stores with React.
Why does this hook exist?
Before React 18, people usually wrote code like this:
1useEffect(() => store.subscribe(forceUpdate), []);This works in simple apps, but it breaks under concurrent rendering because React may:
- start rendering
- pause
- abandon a render
- replay renders
useSyncExternalStore solves this by letting React control:
- when snapshots are read
- when subscriptions fire
- when components re-render
This makes external stores concurrency-safe.
The API in one look
1const value = useSyncExternalStore(
2 subscribe,
3 getSnapshot,
4 getServerSnapshot // optional (SSR)
5);Where:
subscribe(listener)→ tells React how to listen for changes and returns an unsubscribegetSnapshot()→ tells React how to read the current value
Whenever the snapshot changes, React re-renders.
Using useSyncExternalStore with localStorage
localStorage is a perfect example of an external store: it lives outside React, persists across reloads, and can change from other tabs.
Let’s build a small reactive wrapper around it.
Step 1: Create a localStorage store
1function createLocalStorageStore(key, initialValue) {
2 const listeners = new Set();
3
4 const getSnapshot = () => {
5 const data = localStorage.getItem(key);
6 return data ? JSON.parse(data) : initialValue;
7 };
8
9 const setValue = (value) => {
10 localStorage.setItem(key, JSON.stringify(value));
11 listeners.forEach((l) => l());
12 };
13
14 const subscribe = (listener) => {
15 listeners.add(listener);
16
17 const onStorage = (e) => {
18 if (e.key === key) listener();
19 };
20
21 window.addEventListener("storage", onStorage);
22
23 return () => {
24 listeners.delete(listener);
25 window.removeEventListener("storage", onStorage);
26 };
27 };
28
29 return { getSnapshot, setValue, subscribe };
30}This object behaves like a tiny external store:
getSnapshot→ read current valuesetValue→ update localStoragesubscribe→ notify React when something changes
It also listens for the browser’s storage event, so multiple tabs stay in sync.
Step 2: Create a hook using useSyncExternalStore
1import { useSyncExternalStore } from "react";
2
3function useExternalStore(store) {
4 return useSyncExternalStore(
5 store.subscribe,
6 store.getSnapshot,
7 store.getSnapshot
8 );
9}This hook connects React to our external store.
Step 3: Use it in a component
1const counterStore = createLocalStorageStore("counter", 0);
2
3function Counter() {
4 const count = useExternalStore(counterStore);
5
6 return (
7 <div>
8 <p>Count: {count}</p>
9 <button onClick={() => counterStore.setValue(count + 1)}>
10 Increment
11 </button>
12 </div>
13 );
14}Now your app has:
- persistent state
- automatic re-renders
- cross-tab syncing
- concurrent-safe subscriptions
All powered by useSyncExternalStore.
How this compares to useState + useEffect
1const [value, setValue] = useState(() => {
2 return JSON.parse(localStorage.getItem("key")) ?? 0;
3});
4
5useEffect(() => {
6 localStorage.setItem("key", JSON.stringify(value));
7}, [value]);This is perfectly fine for local component state.
But it:
- doesn’t scale well to shared stores
- doesn’t handle cross-tab sync cleanly
- doesn’t solve concurrency edge cases
- doesn’t generalize to external systems
useSyncExternalStore is built specifically to solve those problems.
How this fits into modern state management
Most modern libraries combine two ideas:
- Proxies → track what changed
- useSyncExternalStore → tell React when to update
Proxy systems decide what changed.useSyncExternalStore tells React how and when to re-render.
That’s why this hook is foundational to modern React ecosystems.
When should you use it?
Use it when:
- state lives outside React
- many components read from the same source
- you’re building a store or persistence layer
- you care about concurrent-safe behavior
- you’re writing a library or framework
You probably don’t need it for normal component state.
Final takeaway
useSyncExternalStore is React’s official bridge between:
👉 React’s rendering engine
👉 and the outside world
It allows you to build:
- persistent stores
- shared global state
- reactive systems
- library-grade integrations
If useState is React’s local state primitive,useSyncExternalStore is React’s external state primitive.