How State Management Libraries Work
Under the hood: subscriptions, external stores, and useSyncExternalStore
The Core Question
In the State Architecture section, we saw that Zustand causes only the components that read from the store to re-render. But React components re-render when their state or props change — and Zustand's store lives outside React. So how does an external JavaScript object trigger a React re-render?
The answer is React.useSyncExternalStore — a React 18 built-in hook designed specifically for this purpose. Understanding this mechanism explains not just Zustand, but Redux, Jotai, and any library that manages state outside of React.
The Bridge: useSyncExternalStore
React 18 ships with a hook that lets external stores (anything not managed by useState or useReducer) participate in React's rendering cycle:
1. subscribe(callback)
React calls this once when the component mounts. You register the callback with your store. When the store changes, call the callback — this tells React "something might have changed, check the snapshot."
2. getSnapshot()
React calls this to get the current value. It compares the result with the previous snapshot using Object.is(). If different → re-render. If same → skip.
3. getServerSnapshot()
Optional. Returns the value to use during server-side rendering, where subscriptions don't exist.
How Zustand Uses It
When you call useFormStore() in a component, here's what Zustand does internally:
Let's trace a real update step-by-step:
User types in email input
The onChange handler calls setEmail("a"), which calls Zustand's set() function. The vanilla store merges { email: "a" } into the state object.
Store notifies all subscribers
Zustand's store has an internal Set of listener callbacks. After updating state, it iterates the set and calls every listener. These listeners are the callbacks that React registered via useSyncExternalStore's subscribe argument.
React calls getSnapshot() for each subscriber
React calls the selector function — e.g. (state) => state.email for StickyBar — and compares the result with the previous snapshot using Object.is().
React decides: re-render or skip?
StickyBar selected email, which changed from "" to "a" → Object.is("", "a") is false → re-render. Sidebar never called useFormStore() → no subscription → no check → no re-render.
Context vs External Store: The Fundamental Difference
The key insight is who decides which components re-render:
React Context
State lives inside React (useState/useReducer in the Provider component).
When state updates, React re-renders the Provider → all children re-render (React's normal top-down reconciliation).
Granularity: Provider-level. Every child of the Provider re-renders, whether or not it calls useContext.
Zustand / Redux (External Store)
State lives outside React (plain JavaScript object).
When state updates, the store notifies subscribers → useSyncExternalStore checks each component's selected slice → only re-renders if that slice changed.
Granularity: Component-level. Each component independently decides whether to re-render.
Before useSyncExternalStore: The Old Way
Before React 18, libraries like Zustand and Redux had to use a workaround to connect external stores to React's rendering cycle. The pattern was to force a re-render using useState or useReducer:
This approach worked, but had critical problems:
Problem 1: Tearing in Concurrent Mode
React 18 introduced concurrent rendering, where React can pause and resume renders. With the old pattern, two components reading from the same store during the same render could see different values if the store updated mid-render. This is called "tearing" — the UI becomes inconsistent.
Problem 2: Timing and Race Conditions
The subscription happens in useEffect, which runs after the initial render. If the store updates between the initial render and the effect, the component could miss the update or display stale data.
Problem 3: No SSR Safety
Server-side rendering doesn't run effects, so subscriptions never happen. There was no clean way to provide a server-specific snapshot of the store.
useSyncExternalStore was specifically designed to solve these problems. It guarantees:
- No tearing: All components see a consistent snapshot during concurrent renders
- Synchronous subscription: The subscription happens before the first render, eliminating race conditions
- SSR support: The optional
getServerSnapshotparameter provides a server-safe fallback
Selector Patterns and the Object.is() Gotcha
The selector is the function you pass to useStore. It controls which slice of state the component subscribes to. A good selector makes the difference between a component re-rendering on every store update and re-rendering only when its data changes.
The Comparison Rule
After every store update, useSyncExternalStore calls your selector and compares the result to the previous result using Object.is(). This is the same comparison React uses for useState. For primitives (strings, numbers, booleans), it compares by value. For objects and arrays, it compares by reference.
The Classic Gotcha: Inline Object Selectors
Returning a new object from a selector is the most common Zustand performance mistake. Even if the data inside is identical, a new object reference fails the Object.is() check every time.
How useShallow Works
useShallow wraps your selector with a shallow equality check instead of Object.is(). It compares each field of the returned object individually. The component only re-renders if at least one field changed.
The selector decision
state => state.emailPreferred. Direct Object.is() comparison.
useShallow(state => ({ email, name }))Use useShallow to avoid re-rendering on unrelated changes.
state => state.items.lengthFine if the derived result is a primitive. An inline .filter() that returns a new array has the same problem as an inline object.
useFormStore()Subscribes to the entire store. Acceptable for small stores; a performance problem if the store is large and frequently updated.