Using useSyncExternalStore

Most state in a React app lives in React. Sometimes it doesn't — a class holding drag state, a browser API, a store written before hooks existed. useSyncExternalStore is the supported way to read that state without your UI going out of sync during a concurrent render.

The shape of it

The hook takes a subscribe function and a getSnapshot function. Nothing else.

const value = useSyncExternalStore(
  store.subscribe,
  store.getSnapshot,
  store.getServerSnapshot,
);

subscribe receives a callback and returns an unsubscribe function. getSnapshot returns the current value, and React calls it whenever it needs to check whether anything changed.

The one rule that matters

getSnapshot must return a cached value. If it builds a new object every call, React sees a different reference every time and re-renders forever:

// Wrong — new array on every call
getSnapshot() {
  return [...this.items];
}

// Right — same reference until something actually changes
getSnapshot() {
  return this.snapshot;
}

Keep a snapshot field on the store and replace it only inside the mutation path. That's the whole trick.

The third argument only matters if you server-render. Omit it and React will complain during hydration.

A few things worth knowing:

  • Subscriptions are set up in an effect, so the first paint uses the initial snapshot.
  • subscribe should be stable — define it once, not inline.
  • Selecting a slice needs useSyncExternalStoreWithSelector, not this hook.

A desk

The drag selection demo on this site uses exactly this pattern: one plain class holds the selection, and every selectable item subscribes.