**Short version:** Zustand is the React state manager most 2026 builders reach for over Redux. Made by pmndrs (the same team behind React Three Fiber and Jotai). One-line store creation, no boilerplate, first-class TypeScript. Official docs at zustand.docs.pmnd.rs, source at github.com/pmndrs/zustand.
Install + first store
npm i zustandimport { create } from 'zustand'
interface BearStore {
bears: number
increase: () => void
reset: () => void
}
export const useBears = create<BearStore>()((set) => ({
bears: 0,
increase: () => set((state) => ({ bears: state.bears + 1 })),
reset: () => set({ bears: 0 }),
}))Component usage:
function BearCounter() {
const bears = useBears((s) => s.bears)
return <h1>{bears} bears here</h1>
}
function Controls() {
const increase = useBears((s) => s.increase)
return <button onClick={increase}>one up</button>
}No provider needed. No context wrapper. No boilerplate. That's the entire pitch.
Why Zustand won in 2026
Three reasons Zustand became the default over Redux Toolkit for solo builders and small teams:
- Zero-config setup. No store composition, no combineReducers, no context provider around your app. Just
create()and use. - Selector-based subscriptions. Components re-render only when the exact slice they subscribe to changes. Free performance win vs naive context.
- First-class TypeScript. The double-parens pattern (
create<Type>()(...)) types the entire store correctly, including derived state and actions.
See Best React State Libraries 2026 for how Zustand fits into the broader modern React stack.
Zustand vs Redux Toolkit
| Aspect | Zustand | Redux Toolkit |
|---|---|---|
| Setup lines | 1 | ~10 (store, provider, reducers) |
| TypeScript | First-class | First-class |
| DevTools | Via middleware | Built-in |
| Middleware | Persist, devtools, immer | Full ecosystem |
| Bundle size | ~1KB | ~13KB |
| Learning curve | 15 minutes | ~2 days |
| Best for | Solo builders, startups | Enterprise, large teams |
For 90% of 2026 React apps, Zustand is the right default. Redux Toolkit still wins when you have 20+ engineers, strict middleware requirements, or time-travel debugging is critical.
Zustand vs Jotai vs Valtio
All three are pmndrs libraries. Same team, different philosophies:
- Zustand: global store,
set()-based updates. Best default. - Jotai: atomic state, subscribe to individual atoms. Best for forms + dashboards with 50+ independent state pieces.
- Valtio: proxy-based mutable updates (
state.count++just works). Best for mutation-style ergonomics or migrating from MobX.
Most projects that pick pmndrs pick Zustand first, add Jotai for one specific dashboard route later.
Middleware ecosystem
Zustand ships three core middlewares:
persist — save state to localStorage / sessionStorage automatically:
import { persist } from 'zustand/middleware'
export const useStore = create(persist((set) => ({ ... }), { name: 'my-store' }))devtools — Redux DevTools integration:
import { devtools } from 'zustand/middleware'
export const useStore = create(devtools((set) => ({ ... })))immer — mutable-style updates for nested state:
import { immer } from 'zustand/middleware/immer'
export const useStore = create(immer((set) => ({
todos: [],
add: (todo) => set((s) => { s.todos.push(todo) }),
})))Community middlewares on npm: zundo (undo/redo), zustand-computed (derived state), zustand-persist-and-sync (multi-tab sync), and 20+ others.
Real Zustand patterns 2026
Slicing large stores
For stores over ~10 fields, split into slices:
const createBearSlice = (set) => ({
bears: 0,
increase: () => set((s) => ({ bears: s.bears + 1 })),
})
const createFishSlice = (set) => ({
fishes: 0,
addFish: () => set((s) => ({ fishes: s.fishes + 1 })),
})
export const useStore = create()((...a) => ({
...createBearSlice(...a),
...createFishSlice(...a),
}))Selector shallow comparison
By default Zustand uses reference equality. For array/object selectors, wrap with shallow:
import { shallow } from 'zustand/shallow'
const { bears, fishes } = useStore((s) => ({ bears: s.bears, fishes: s.fishes }), shallow)Async actions
No middleware needed. Just make your action async:
const useStore = create((set) => ({
data: null,
loading: false,
fetchData: async () => {
set({ loading: true })
const data = await fetch('/api/data').then(r => r.json())
set({ data, loading: false })
},
}))What Zustand doesn't do
Zustand is a store, not a framework. It doesn't:
- Handle server state — use TanStack Query or SWR for that
- Handle routing state — use Next.js router, TanStack Router, or React Router
- Handle forms — use React Hook Form or Tanstack Form
The pmndrs philosophy: small, composable, unopinionated pieces. Zustand handles client state. Everything else picks a specialized tool.
Ship it
Install `zustand`, import `create` from `zustand`, write your first store in 5 minutes. Docs at zustand.docs.pmnd.rs, source + issues at github.com/pmndrs/zustand.
See AI Stack for 2026 for the broader modern React + Next.js stack Zustand fits into, and Best AI Dev Tools Ranked for the IDEs that ship this fastest.