Zustand vs Jotai vs Valtio (2026 State Management)
All three are created by the same team (Daishi Kato / Poimandres). All three are tiny, fast, and React-focused. But they follow fundamentally different paradigms. Here's how to pick the right one.
Quick Verdict
- Zustand — Best all-rounder. Simplest mental model. Default choice.
- Jotai — Best for atomic state. Great for complex, interconnected state.
- Valtio — Best for mutable-style state. Closest to "plain JavaScript objects."
The Mental Models
Zustand: One Store, Selectors
Create a store. Components subscribe to slices of it.
import { create } from 'zustand'
const useStore = create((set) => ({
count: 0,
name: 'John',
increment: () => set((s) => ({ count: s.count + 1 })),
setName: (name) => set({ name }),
}))
// Component only re-renders when count changes
function Counter() {
const count = useStore((s) => s.count)
const increment = useStore((s) => s.increment)
return <button onClick={increment}>{count}</button>
}
Mental model: Redux-like store, but simple. One object holds state. Selectors control re-renders.
Jotai: Atoms (Bottom-Up)
Create independent atoms. Compose them. Components subscribe to specific atoms.
import { atom, useAtom } from 'jotai'
const countAtom = atom(0)
const nameAtom = atom('John')
const doubleCountAtom = atom((get) => get(countAtom) * 2) // Derived
function Counter() {
const [count, setCount] = useAtom(countAtom)
return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}
Mental model: Like React's useState, but global and composable. Each piece of state is an independent atom. Atoms can derive from other atoms.
Valtio: Mutable Proxy
Create a proxy object. Mutate it directly. Components auto-track which properties they use.
import { proxy, useSnapshot } from 'valtio'
const state = proxy({
count: 0,
name: 'John',
})
function Counter() {
const snap = useSnapshot(state)
return <button onClick={() => { state.count++ }}>{snap.count}</button>
}
Mental model: Plain JavaScript. Mutate objects normally. Valtio tracks access and triggers re-renders. No selectors, no atoms, just objects.
Feature Comparison
| Feature | Zustand | Jotai | Valtio |
|---|---|---|---|
| Bundle size | ~1.1KB | ~2.4KB | ~3.5KB |
| API surface | Small | Small | Smallest |
| Learning curve | Low | Medium | Lowest |
| TypeScript | Excellent | Excellent | Good |
| Devtools | ✅ Redux devtools | ✅ Custom devtools | ✅ Devtools proxy |
| Middleware | ✅ (persist, devtools, immer) | Atoms composable | ❌ Limited |
| SSR support | ✅ | ✅ | ✅ (needs care) |
| React Suspense | ❌ | ✅ (async atoms) | ❌ |
| Outside React | ✅ (subscribe/getState) | ❌ (React-only) | ✅ (proxy is plain JS) |
Re-Render Performance
All three are efficient, but the optimization strategies differ:
Zustand: You control re-renders with selectors. useStore(s => s.count) only re-renders when count changes. Forgot a selector? Component re-renders on any store change.
Jotai: Automatic. Components only re-render when the atoms they use change. No selectors needed. Most granular by default.
Valtio: Automatic via proxy tracking. useSnapshot detects which properties the component accesses and only re-renders when those change. Magic but sometimes surprising.
Winner: Jotai for default granularity. Zustand for explicit control.
When to Use Each
Choose Zustand When
- You want a single source of truth (one store)
- Coming from Redux (familiar pattern)
- Need to access state outside React (in utilities, middleware)
- Want the simplest setup
- Building a new project and want a safe default
Choose Jotai When
- State is naturally atomic (many independent pieces)
- You need derived/computed state (atom A depends on atoms B and C)
- Using React Suspense with async data
- Building a complex form with many interdependent fields
- State graph is interconnected
Choose Valtio When
- Team prefers mutable-style code (less functional programming)
- Building prototypes fast (lowest boilerplate)
- Working with deeply nested objects
- Coming from Vue/MobX (similar reactivity model)
- Need to share state between React and non-React code
Real-World Examples
Todo App
Zustand:
const useTodos = create((set) => ({
todos: [],
addTodo: (text) => set((s) => ({ todos: [...s.todos, { text, done: false }] })),
toggleTodo: (i) => set((s) => ({
todos: s.todos.map((t, idx) => idx === i ? { ...t, done: !t.done } : t)
})),
}))
Jotai:
const todosAtom = atom([])
const addTodoAtom = atom(null, (get, set, text) => {
set(todosAtom, [...get(todosAtom), { text, done: false }])
})
Valtio:
const state = proxy({ todos: [] })
const addTodo = (text) => state.todos.push({ text, done: false })
const toggleTodo = (i) => { state.todos[i].done = !state.todos[i].done }
Valtio is clearly the most concise for mutable operations. Zustand and Jotai require immutable updates.
Persistence
Zustand: Built-in persist middleware.
const useStore = create(persist((set) => ({ count: 0 }), { name: 'my-store' }))
Jotai: atomWithStorage utility.
const countAtom = atomWithStorage('count', 0)
Valtio: Manual or proxyWithPersist.
// Manual
subscribe(state, () => localStorage.setItem('state', JSON.stringify(state)))
Winner: Zustand for built-in middleware. Jotai close second.
FAQ
Can I use multiple libraries together?
Yes, but why? Pick one. If you need Zustand for global state and Jotai for form atoms, that works but adds complexity.
Which is best for Next.js?
All three work with Next.js. Zustand has the best SSR story (serialize store, rehydrate on client). Jotai works well with React Server Components.
Which is best for large apps?
Zustand for structured state with clear boundaries. Jotai for complex interconnected state. Avoid Valtio for large apps (proxy tracking gets hard to debug).
Should I still use Redux?
For new projects, no. Zustand covers the same use cases with 1/10th the boilerplate. Redux is fine if your team knows it, but there's no reason to start with it in 2026.
What about React Context?
Context works for low-frequency updates (theme, locale, auth). For high-frequency state (forms, real-time data, UI state), use Zustand/Jotai/Valtio.
Bottom Line
Zustand is the default choice for 2026. Simple, tiny, works everywhere. Use Jotai when your state is naturally atomic and you need granular reactivity. Use Valtio when you want the lowest boilerplate and mutable-style code.
All three are excellent. You can't go wrong. Pick based on your mental model preference and start building.