skip to content

In React, what three arguments does useSyncExternalStore take, what is each one required to do, and what does the hook return?

level: middleimportance: should knowfreq 45%

answer

  1. subscribe, read, and a server fallback
  2. React hands you the change callback
  3. unsubscribe function is the return value
  4. Object.is on consecutive snapshots
  5. third argument only for server rendering

basics

~20 s

useSyncExternalStore takes subscribe (registers React's change callback and returns an unsubscribe function), getSnapshot (reads the store's current value), and an optional getServerSnapshot used during server rendering. It returns the current snapshot and re-renders the component when that snapshot changes.

solid answer

~50 s

The signature is `useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)` and it returns the current snapshot value. `subscribe` is called with a callback that React owns: you register it with the store, call it on every change, and return a function that unregisters it. `getSnapshot` is a pure read of the store's current value; React calls it during render and again after a change notification, comparing consecutive results with `Object.is` to decide whether to re-render. The optional `getServerSnapshot` supplies a value during server rendering and the initial hydration render, where browser-only sources are unavailable. One detail candidates miss: the identity of `subscribe` matters — pass a new function on a re-render and React will unsubscribe and resubscribe, so define it at module scope or memoize it. This is the sanctioned way to read external mutable state without tearing under concurrent rendering.

code

javascript · 21 lines
javascript
import { useSyncExternalStore } from 'react';

const QUERY = '(min-width: 768px)';

function subscribe(onStoreChange) {
  const mql = window.matchMedia(QUERY);
  mql.addEventListener('change', onStoreChange);
  return () => mql.removeEventListener('change', onStoreChange);
}

function getSnapshot() {
  return window.matchMedia(QUERY).matches;
}

function getServerSnapshot() {
  return false;
}

export function useIsWideScreen() {
  return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
}

go deeper

for a junior

Be able to say this hook connects a component to state React does not own, and that you hand it a way to subscribe plus a way to read the current value.

for a middle

Explain each argument precisely: subscribe registers React's callback and returns an unsubscribe function, getSnapshot is a pure read compared with Object.is, and the third argument covers server rendering.

for a senior

Demonstrate that subscribe identity drives resubscription, so it belongs at module scope or in useCallback, and that getSnapshot runs during render and must therefore stay cheap and pure.

for a principal

Own the judgment of when this primitive is warranted at all: it is the sanctioned bridge to genuinely external mutable state, and pulling data into it that React could hold itself adds a subscription you now have to reason about.

## What counts as an external store An external store is any state that lives outside React's own state: a Redux or Zustand store, a `window.matchMedia` query, `navigator.onLine`, the browser History, a WebSocket cache, a third-party observable. React does not own that data, so it has no way to know when it changed. `useSyncExternalStore` is React's official primitive for reading such a source safely, and it exists because concurrent rendering can pause and resume a render — without a sanctioned subscription, two components reading the same mutable source mid-render can end up showing different values (tearing). That one clause is the *why*; the rest of this answer is the *what*. ## The signature ```js const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); ``` ### subscribe(onStoreChange) React passes **its own** callback in. Your job is to register that callback with the store and return a function that unregisters it: ```js function subscribe(onStoreChange) { store.listeners.add(onStoreChange); return () => store.listeners.delete(onStoreChange); } ``` Two rules follow. First, the return value must be the unsubscribe function — not the snapshot, not `undefined`. Second, the *identity* of `subscribe` is meaningful: if you pass a different function on a re-render, React tears down the old subscription and creates a new one. An inline arrow declared in the component body is a new function every render, so it churns subscriptions. Define `subscribe` at module scope when it closes over nothing, or wrap it in `useCallback` when it must close over props. ### getSnapshot() A pure, cheap read of the store's current value. React calls it during render, and calls it again whenever `onStoreChange` fires to see whether anything actually changed. The comparison is `Object.is` on consecutive return values: same value, no re-render; different value, React re-renders with the new snapshot. Because the check is identity-based, `getSnapshot` must return a value that is stable while the store is unchanged — a primitive, or an object reference the store recomputes on write rather than on read. Returning a freshly built object on every call makes the comparison never bail out. ### getServerSnapshot() Optional, and only relevant to server-rendered apps. React uses it while rendering on the server and again for the initial hydration render on the client, because `getSnapshot` typically touches browser globals that do not exist server-side and would produce markup that hydration cannot match. ## What the hook returns Just the snapshot — the value `getSnapshot` produced. There is no tuple, no setter, no pending flag. Writing to the store is the store's own concern; the hook is read-only from React's side. ## A complete example ```js import { useSyncExternalStore } from 'react'; function subscribe(onStoreChange) { window.addEventListener('online', onStoreChange); window.addEventListener('offline', onStoreChange); return () => { window.removeEventListener('online', onStoreChange); window.removeEventListener('offline', onStoreChange); }; } function getSnapshot() { return navigator.onLine; } function getServerSnapshot() { return true; } function useOnlineStatus() { return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); } ``` The snapshot here is a boolean, so identity is never a problem, and the subscribe function is module-scoped, so React subscribes once. ## Practical notes worth saying out loud - **There is no selector argument.** The hook always returns whatever `getSnapshot` returns. Narrowing a large store to one field is something you do inside `getSnapshot` (and then you own the caching problem), which is exactly why state libraries wrap this hook rather than exposing it directly. - **It does not replace effects.** It is a read primitive for external state, not a general side-effect mechanism. - **Updates from it are treated as urgent.** React does not defer a store change the way it can defer a transition, which is part of how it keeps the UI consistent with the store. - **Application code rarely calls it directly.** The typical direct uses are small browser-API hooks like the one above; large stores come with their own hook built on top of it. In an interview, name the three parameters, state the contract of each in one sentence, and add the subscribe-identity detail — that last point is what separates someone who has read the docs from someone who has wired a store up.

  • What happens if you declare the subscribe function inline in the component body?
    It is a new function on every render, so React unsubscribes from the store and resubscribes after each render. That is wasted work, and with a store that emits on subscribe it can turn into a loop. Move it to module scope, or wrap it in `useCallback` with the props it closes over as dependencies.
  • Why does the hook return only a value instead of a value and a setter like useState?
    Because the store, not React, owns the data. Updates go through the store's own API, which then notifies subscribers via the callback React registered. Giving the hook a setter would imply React owns the state, which is exactly the split this primitive is designed to keep clear.
  • Can you call useSyncExternalStore more than once in a component for different slices of the same store?
    Yes. Each call is an independent subscription with its own `getSnapshot`, and React re-renders when any of them reports a changed value. It is a common way to subscribe to two narrow slices instead of one wide snapshot, at the cost of more subscriptions.

saying these in an interview costs you the question

  • Says subscribe should return the current snapshot
  • Thinks React polls the store on a timer
  • Claims the second argument is a selector function
  • Declares subscribe inline and calls the churn harmless
  • Believes the hook replaces useEffect for all side effects

context