Redux owned a decade of React and now lives as legacy across most new projects. Zustand, Jotai, and signals cut re-render costs to levels the context API never reached. Render benchmarks and application type decide between the three models.

Redux: where it still pays

Large teams value mature devtools with time-travel, the middleware ecosystem, and patterns the framework enforces. createSlice and RTK Query shrank boilerplate and cover mutations and cache without extra ceremony. Discipline remains mandatory: a subscription through a bad selector triggers re-render storms. The React documentation on useSyncExternalStore explains how unstable selectors turn the store into a render bottleneck.

Zustand: one store, zero providers

One store, hooks as selectors, zero providers, no dispatch. Each component subscribes to the slice it uses, and equality functions (shallow from zustand/shallow) control re-renders down to the field level. The package weighs a bit over 1 kB minified, and the persist middleware syncs with localStorage in two lines. The official documentation describes the per-field selection pattern.

Jotai: chained derivations

Jotai builds state bottom-up: derived atoms compute from other atoms, and async atoms suspend through native React Suspense. The model handles complex dependent state such as multi-step form wizards and combinatorial live filters. When derivation dominates the domain, Jotai saves code and bugs. The Jotai documentation lists the atom composition patterns.

Signals: reactivity outside the render cycle

Signals touch the exact DOM node and skip component re-renders. Solid.js proves the model in production, Preact ships signals in its public API, and the TC39 signals proposal advances standardization inside React itself. React interop still carries friction: evaluate signals per project, where the stack allows.

Benchmark: 10k rows

A table with 10k rows updating one cell: context-based state rerenders the page, Zustand with selectors rerenders the row, signals update the cell. Commit-time differences reach orders of magnitude. The longer the list and the more frequent the writes, the wider the gap between models.

Selection criteria

  • Server state dominates? TanStack Query carries the weight while client state shrinks to UI concerns
  • General case? Zustand as the pragmatic default
  • Chained derivations everywhere? Jotai
  • Render performance is contractual (real-time dashboards, editors)? Signals where the stack allows

Incremental migration

Coexistence works: turn context providers into Zustand stores one domain per PR. Keep Redux where devtools workflows are part of how the team debugs. Adopt signals per island in hybrid pages, starting with the components that update most.