useSyncExternalStoreHook
useSyncExternalStore Hook — React's connection to external stores
React 18 introduced a new hook — useSyncExternalStore, designed to safely and stably connect React to any external store or data source. It ensures that React components synchronize with the latest data value without race conditions.
As a result, React always renders with consistent data, even if the store changes during rendering. This is the hook that modern libraries like Redux, Zustand, jotai, Recoil are built upon.
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)
Let's create a small JavaScript store that stores a value and allows subscribing to changes.
// store.js
let count = 0;
let listeners = new Set();
export const store = {
getSnapshot() {
return count;
},
subscribe(callback) {
listeners.add(callback);
return () => listeners.delete(callback);
},
increment() {
count++;
listeners.forEach((cb) => cb());
}
};
Here we have a simple counter store that can: ✅ store the value (`count`) ✅ notify all subscribers when count changes
// Counter.js
import React from 'react';
import { store } from './store';
import { useSyncExternalStore } from 'react';
export default function Counter() {
const count = useSyncExternalStore(
store.subscribe,
store.getSnapshot
);
return (
<div>
<h2>Count: {count}</h2>
<button onClick={store.increment}>+1</button>
</div>
);
}
In this case, React always gets the latest value from the `store` and only re-renders when the store actually changes. This is much more efficient and secure than using `useEffect` or `useState` for store synchronization.
Previously, many libraries (e.g., older versions of Redux) used useEffect to get data from the store and notify React. However, during concurrent rendering, this caused race conditions — when React would render with old values.
useSyncExternalStore solves this problem by guaranteeing that during rendering, React always uses the latest consistent snapshot.
For example, you have a websocket receiving real-time data. useSyncExternalStore can be used for React components to get the latest values without re-render lag.
import React, { useSyncExternalStore } from 'react';
function createSocketStore() {
let data = 'waiting...';
let listeners = new Set();
const ws = new WebSocket('wss://example.com/socket');
ws.onmessage = (e) => {
data = e.data;
listeners.forEach((cb) => cb());
};
return {
subscribe(cb) {
listeners.add(cb);
return () => listeners.delete(cb);
},
getSnapshot() {
return data;
}
};
}
const socketStore = createSocketStore();
export default function LiveData() {
const value = useSyncExternalStore(
socketStore.subscribe,
socketStore.getSnapshot
);
return <div>📡 Latest data: {value}</div>;
}
Here React receives live data from the socket, but always consistently — without the risk of rendering reaching old values.
If you're using a store in SSR projects (e.g., Next.js), you can provide a server-side snapshot — for example, a default value.
const count = useSyncExternalStore(
store.subscribe,
store.getSnapshot,
() => 0 // Server-side default
);
This way React won't complain about server and client snapshots being different during hydration.
Write a small store that:
Try doing the same with useEffect/useState version and see the difference — in render consistency and re-render speed.