Lesson 47 of 80
The hook for subscribing to state React doesn't own — a browser API, an event emitter, a third-party store — without the tearing bug a homemade useState-plus-useEffect subscription can hit.
15 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
Most state a component reads is state React itself manages — useState, useReducer, props. But sometimes the value you need lives outside React entirely: navigator.onLine, window.innerWidth, a Redux store, a custom event emitter. Reading external state naively (subscribe in a useEffect, mirror it into useState) works most of the time, but it can tear under concurrent rendering: React is allowed to pause a render partway through and resume later, and if the external value changes in that gap, different components in the same tree can end up reading different snapshots of it during what's supposed to be one consistent render.
useSyncExternalStore is the hook built specifically to close that gap. It guarantees every component reading the same external source during a single render sees the same snapshot, even if the store changes mid-render.
navigator.onLine hookfunction subscribe(callback) {
window.addEventListener("online", callback);
window.addEventListener("offline", callback);
return () => {
window.removeEventListener("online", callback);
window.removeEventListener("offline", callback);
};
}
function useOnlineStatus() {
return useSyncExternalStore(
subscribe, // how to listen for changes
() => navigator.onLine, // getSnapshot: current value, client
() => true // getServerSnapshot: value during SSR
);
}
function StatusBanner() {
const isOnline = useOnlineStatus();
return isOnline ? null : <p role="status">You're offline</p>;
}
The three arguments are the whole contract: subscribe registers a callback and returns an unsubscribe function, getSnapshot returns the current value synchronously, and getServerSnapshot supplies a value to use during server rendering where navigator.onLine doesn't exist at all. React calls getSnapshot before and after every render to check whether the external value actually changed, and re-renders every subscribed component together if it did — that's what makes the guarantee hold.
useState and useReducer are already safe under concurrent rendering — useSyncExternalStore exists only for values React doesn't manage itself.getSnapshot every call is the most common bug, and it's easy to write by accident with a spread or a .map() inside the snapshot function — always return the same reference when the underlying data hasn't changed.react-redux's useSelector use useSyncExternalStore internally now — knowing this hook is mostly about understanding why those libraries stopped tearing under React 18's concurrent features, not about hand-writing subscriptions yourself every day.getServerSnapshot is easy to forget and only matters for SSR. Skipping it (or returning something that doesn't match the client's first render) can produce a hydration mismatch, the same class of bug covered in the hydration lesson.The tearing risk this hook closes only exists because React can interrupt a render partway through and resume it later — the exact capability covered in the earlier lesson on React's concurrent rendering engine. useSyncExternalStore is a purpose-built guarantee layered on top of that interruptibility, specifically for state living outside React's own updates.