useSyncExternalStore

Subscribe to State Outside of React

useSyncExternalStore

When state lives outside React — in a global store, a browser API, or a third-party library — `useSyncExternalStore` is the safe way to read and re-render on changes.

4 min read Level 3/5 #react#useSyncExternalStore#hooks
What you'll learn
  • Subscribe to an external state source
  • Avoid race conditions with concurrent rendering

Most state lives inside React (useState, useReducer). But sometimes you need to subscribe to external state — a global store like Redux/Zustand, a browser API like window.matchMedia, or anything that isn’t React.

useSyncExternalStore(subscribe, getSnapshot) is the official hook for that.

The Shape

const value = useSyncExternalStore(
  subscribe,     // (listener) => unsubscribe
  getSnapshot,   // () => current value
);

subscribe is called once. It should attach a listener and return a function to detach it. Whenever the listener is called, React calls getSnapshot and re-renders if the value changed.

Example: Online Status

function useOnlineStatus() {
  return useSyncExternalStore(
    listener => {
      window.addEventListener("online",  listener);
      window.addEventListener("offline", listener);
      return () => {
        window.removeEventListener("online",  listener);
        window.removeEventListener("offline", listener);
      };
    },
    () => navigator.onLine
  );
}

getSnapshot returns the current value (a boolean here). When navigator.onLine changes, the event listeners fire, React calls getSnapshot, sees a new value, re-renders.

Why Not Just useEffect + useState?

You can write a similar hook with useState + useEffect. But:

  • It can tear during concurrent renders (different parts of a render see different values)
  • You have to wire up the initial sync yourself

useSyncExternalStore is the React-team-recommended primitive for “safely subscribe to external state”. State libraries (Zustand, Redux Toolkit) use it internally.

Returning Objects

Each call to getSnapshot should return the same reference if the value hasn’t changed. Returning a new object every time will make React think the state changed every render and re-render in a loop:

// ✗ New object every snapshot → infinite re-renders
() => ({ width: window.innerWidth })

// ✓ Memoize, or return a primitive
() => window.innerWidth

You’ll Use It Through Libraries

In day-to-day code, you’ll rarely call useSyncExternalStore directly. You’ll use a state library’s hook (useStore in Zustand, useSelector in Redux) — those wrap useSyncExternalStore for you.

It’s still good to know what’s underneath.

Up Next

A newer hook for separating the “non-reactive” parts of an effect.

useEffectEvent →