An interactive demo of React 18's useSyncExternalStore: a plain JavaScript store lives outside React, components subscribe with a selector, and updates from anywhere — a button, a timer, or the browser console — re-render every subscriber in sync, with no tearing.
▶ Live: https://usesyncexternalstore.vercel.app/
It's the primitive Redux, Zustand, and Jotai use under the hood.
The store is plain JS and knows nothing about React:
let state = { count: 0 };
const listeners = new Set();
const store = {
getSnapshot: () => state,
subscribe: (cb) => { listeners.add(cb); return () => listeners.delete(cb); },
increment: () => {
state = { ...state, count: state.count + 1 }; // immutable update
listeners.forEach((l) => l()); // notify subscribers
},
};A component subscribes with one hook:
function Counter() {
const count = useSyncExternalStore(
store.subscribe, // how to listen
() => store.getSnapshot().count // what to read (a selector)
);
return <span>{count}</span>;
}That's it. React manages the subscription, reads a consistent snapshot, and re-renders when the selected value changes.
- Components A and B both read
countfrom the same store. Click any button — includingstore.increment(), which sets no React state — and both update in sync. One source of truth, outside React. - A Ticker reads
ticks, which asetIntervaloutside React bumps every second. Its render count climbs on its own, with zero React state driving it. - Selector isolation. A and B select
count; the ticker changesticks. Because their selector returns a primitive that didn't change, React skips their re-render — only the Ticker re-renders each second. Watch the render counters: A and B hold steady while the Ticker's climbs. - Open the console and type
__store.increment()— the UI still updates. The store doesn't care who mutates it.
The old pattern (subscribe in an effect, mirror the snapshot into useState) works for simple cases but can tear under React 18 concurrent rendering: different components can read the store at different moments within one render and show inconsistent values in the same frame. useSyncExternalStore guarantees every component reads the same consistent snapshot per render, forces a synchronous update when needed, and handles subscribe/unsubscribe for you. It also takes an optional getServerSnapshot for SSR.
React 19 · TypeScript · Vite.
npm install
npm run devMIT © 2026 dev48v — dev48v.infy.uk