state

useSyncExternalStore

Subscribes a component to an external store in a way that is safe with concurrent rendering, preventing tearing. Reach for it when writing a library or reading from a store that lives outside React (Redux, Zustand, browser APIs, custom event emitters).

const value = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)

Parameters & Behavior

Parameter Purpose
subscribe (onStoreChange) => unsubscribe — React calls onStoreChange when the store changes
getSnapshot Returns the current store value; must be referentially stable when unchanged
getServerSnapshot Snapshot used during SSR — required if you server-render this component
tearing-free All consumers see the same value within a single render pass

Examples

const width = useSyncExternalStore(
  (cb) => { window.addEventListener('resize', cb); return () => window.removeEventListener('resize', cb); },
  () => window.innerWidth,
  () => 0
);

Read window width with SSR-safe fallback

const online = useSyncExternalStore(
  (cb) => { window.addEventListener('online', cb); window.addEventListener('offline', cb); return () => { window.removeEventListener('online', cb); window.removeEventListener('offline', cb); }; },
  () => navigator.onLine,
  () => true
);

Track online/offline state safely

function useStore(selector) {
  return useSyncExternalStore(store.subscribe, () => selector(store.getState()), () => selector(store.getInitialState()));
}

Selector hook pattern used by lightweight state libraries

const theme = useSyncExternalStore(mediaSubscribe, () => mql.matches ? 'dark' : 'light');

Reactively read a matchMedia query

Gotcha

getSnapshot must return the SAME reference when the underlying value hasn't changed — recreating objects inside it causes an infinite render loop. Provide getServerSnapshot whenever the component might be server-rendered.

Related hooks

← All React hooks · Fix hydration mismatch