Knowledge base
CodexGuild Knowledge Base

Zustand v5 (5.0.15): no custom equality in `create`, `useShallow` for object selectors

as of Aug 13, 2026 · applies to zustand >= 5.0 · canonical · codexguild.com/kb/kb-zustand-v5-gotchas-2026 · exported 2026-10-11
Canonical as of Aug 13, 2026

Zustand v5 (5.0.15): no custom equality in `create`, `useShallow` for object selectors

Zustand is at 5.0.15 (2026-08-13); v5 stable since 2024-10-14. `create` takes no equality fn, and selectors returning new objects/arrays loop infinitely (use `useShallow`). No default export, React 18+. 5.0.9 added experimental `unstable_ssrSafe`.

Zustand v5 — current state and gotchas

As of: 2026-10

Versions

  • Latest: zustand 5.0.15 (2026-08-13). v5.0.0 was released 2024-10-14. There is no v6.
  • Since then there have only been 5.0.x patches. Most fix the persist and devtools middleware, for example the rehydration races fixed in 5.0.10 and 5.0.15.
  • v4 receives occasional patches (4.5.7, 2025-05-15). Do not write new code against the v4 APIs.
  • Peer dependencies: react >=18, plus optional immer >=9.0.6 and use-sync-external-store >=1.2.0. The minimum TypeScript version is 4.5.

v5 breaking changes that agents still emit as v4 code

  1. No default export. Use import { create } from 'zustand'.
  2. create hooks take no equality function. Writing useStore(selector, shallow) is v4 code. Use one of these:
import { useShallow } from 'zustand/shallow'
const { count, text } = useStore(useShallow((s) => ({ count: s.count, text: s.text })))
// or keep the v4 API (requires: npm i use-sync-external-store)
import { createWithEqualityFn as create } from 'zustand/traditional'
  1. Selectors must return stable references. v5 matches React's behavior. A selector that builds a new array or object, or returns an inline fallback function, triggers "Maximum update depth exceeded":
useStore((s) => [s.a, s.setA])                 // infinite loop in v5
useStore(useShallow((s) => [s.a, s.setA]))     // OK
const NOOP = () => {}; useStore((s) => s.action ?? NOOP) // stable fallback
  1. Stricter setState(…, true) typing. With replace: true you must pass the complete state, so setState({}, true) is a type error.
  2. persist no longer writes the initial state to storage when the store is created. This also changed in 4.5.5. If you relied on that write, call setState after creation.
  3. UMD/SystemJS builds and ES5 output are dropped.

Newer additions (5.0.x)

  • 5.0.9 (2025-11-30): experimental unstable_ssrSafe middleware for Next.js/SSR usage (see pmndrs/zustand discussion #2740). The unstable_ prefix means the API can still change; it is not a replacement for the documented per-request store pattern.
  • 5.0.11: persist no longer relies on global localStorage, and immer middleware typing with slices is fixed.
  • 5.0.12: the onRehydrateStorage post-rehydration callback receives the latest state.
  • 5.0.13 – 5.0.15:
    • The devtools caller-name detection works on Firefox and Safari.
    • Devtools type inference is improved.
    • clearStorage() invalidates a concurrent async rehydration.

What to do now

  • When reviewing generated code, search for create( hooks called with a second equality argument, and for selectors that return object or array literals. Wrap those selectors in useShallow.
  • For SSR frameworks, keep using a per-request store created in a React context provider. Only adopt unstable_ssrSafe deliberately.
  • Pin to ^5.0.15 to get the persist rehydration fixes.

Sources