The Problem: External State Sources
Some data doesn’t live in React state at all — browser APIs like window.innerWidth, navigator.onLine, or a third-party state library’s store. Subscribing to these with a plain useEffect + useState pattern can produce subtly inconsistent UI under React’s concurrent rendering features.
What useSyncExternalStore Does
useSyncExternalStore(subscribe, getSnapshot) reads a value from an external store and automatically re-renders the component whenever that store changes, in a way that stays consistent even with concurrent rendering — this is the same mechanism state management libraries like Redux and Zustand use internally.
Subscribing to the Browser’s Online Status
import { useSyncExternalStore } from "react";
function subscribe(callback) {
window.addEventListener("online", callback);
window.addEventListener("offline", callback);
return () => {
window.removeEventListener("online", callback);
window.removeEventListener("offline", callback);
};
}
function getSnapshot() {
return navigator.onLine;
}
function useOnlineStatus() {
return useSyncExternalStore(subscribe, getSnapshot);
}
function StatusBadge() {
const isOnline = useOnlineStatus();
return <span>{isOnline ? "🟢 Online" : "🔴 Offline"}</span>;
}useSyncExternalStore Arguments
| Argument | Purpose |
|---|---|
| subscribe | A function that subscribes a callback to store changes, returning an unsubscribe function |
| getSnapshot | A function that returns the store’s current value |
| getServerSnapshot (optional) | A snapshot to use during server rendering, if the store doesn’t exist on the server |
Why Not Just useEffect + useState?
A manual useEffect subscription can read a stale snapshot during React’s concurrent rendering, since the effect runs slightly after render. useSyncExternalStore is specifically designed to always return a value that’s torn-free and consistent with the rest of the render, even if the store changes mid-render.
Server Snapshot for SSR
function useOnlineStatus() {
return useSyncExternalStore(
subscribe,
() => navigator.onLine,
() => true // getServerSnapshot: assume "online" during server rendering
);
}Best Practice
Reach for useSyncExternalStore only when subscribing directly to a genuinely external store outside of React’s own state (a browser API, a custom event emitter, or a non-React library) — for anything backed by React state itself, useState/useReducer/Context are the right tools.