← Back to articles

Zustand vs Jotai vs Valtio (2026 Comparison)

All three are created by the same developer (Daishi Kato) and the pmndrs collective. They solve state management differently. Zustand uses stores. Jotai uses atoms. Valtio uses proxies. Here's which one to pick.

Quick Verdict

  • Zustand — Best general-purpose store. Simple, flexible, most popular.
  • Jotai — Best for granular, composable state. React-centric.
  • Valtio — Best for mutable-style state. Easiest if you come from Vue/MobX.

API Comparison

Zustand — Store-Based

import { create } from 'zustand'

const useStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
}))

function Counter() {
  const count = useStore((state) => state.count)
  const increment = useStore((state) => state.increment)
  return <button onClick={increment}>{count}</button>
}

Jotai — Atom-Based

import { atom, useAtom } from 'jotai'

const countAtom = atom(0)
const doubleAtom = atom((get) => get(countAtom) * 2) // Derived atom

function Counter() {
  const [count, setCount] = useAtom(countAtom)
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}

Valtio — Proxy-Based

import { proxy, useSnapshot } from 'valtio'

const state = proxy({ count: 0 })

function Counter() {
  const snap = useSnapshot(state)
  return <button onClick={() => state.count++}>{snap.count}</button>
}

Key Differences

FeatureZustandJotaiValtio
Mental modelExternal storeReact atoms (like signals)Mutable proxy
Re-render controlSelector-basedAtom-levelAutomatic (proxy tracking)
BoilerplateLowLowestLowest
Works outside React❌ (React-only)
DevTools
Middleware✅ (persist, devtools, immer)✅ (atomWithStorage, etc.)✅ (devtools, subscribeKey)
Bundle size~1KB~2KB~3KB
npm downloads/week~4M~1.5M~500K

Re-Rendering Behavior

This is the most important practical difference.

Zustand

Components only re-render when their selected state changes:

// Only re-renders when count changes, not when other state changes
const count = useStore((state) => state.count)

You must write selectors. Forgetting a selector causes unnecessary re-renders:

// ❌ Re-renders on ANY state change
const store = useStore()

// ✅ Re-renders only when count changes
const count = useStore((s) => s.count)

Jotai

Each atom is independently subscribed. Components only re-render when atoms they use change. No selectors needed — granularity is built in.

// Only re-renders when countAtom changes
const [count] = useAtom(countAtom)

Valtio

Proxy tracks which properties your component accesses. Only re-renders when accessed properties change. Automatic — no selectors, no atoms.

// Valtio tracks that you accessed snap.count
// Only re-renders when count changes
const snap = useSnapshot(state)
return <div>{snap.count}</div>

Winner: Jotai and Valtio for automatic granularity. Zustand requires manual selectors.

Composability

Zustand

One big store (or split into multiple). Stores are standalone:

const useAuthStore = create(...)
const useCartStore = create(...)

Jotai

Atoms compose naturally. Derived atoms depend on other atoms:

const firstNameAtom = atom('John')
const lastNameAtom = atom('Doe')
const fullNameAtom = atom((get) => `${get(firstNameAtom)} ${get(lastNameAtom)}`)

This is Jotai's superpower. Complex state derives from simple atoms.

Valtio

Derived state uses derive or computed properties:

const state = proxy({
  firstName: 'John',
  lastName: 'Doe',
  get fullName() { return `${this.firstName} ${this.lastName}` },
})

Winner: Jotai for composable, derived state.

Async State

Zustand

Handle async in actions:

const useStore = create((set) => ({
  data: null,
  loading: false,
  fetch: async () => {
    set({ loading: true })
    const data = await fetchData()
    set({ data, loading: false })
  },
}))

Jotai

Async atoms with Suspense:

const dataAtom = atom(async () => {
  const res = await fetch('/api/data')
  return res.json()
})

// Component suspends until data loads
function DataView() {
  const [data] = useAtom(dataAtom)
  return <div>{data.name}</div>
}

Valtio

Handle async by mutating the proxy:

const state = proxy({ data: null, loading: false })

async function fetchData() {
  state.loading = true
  state.data = await fetch('/api/data').then(r => r.json())
  state.loading = false
}

Winner: Jotai's Suspense integration is cleanest. Zustand and Valtio are straightforward.

Persistence

Zustand

Built-in persist middleware:

const useStore = create(
  persist(
    (set) => ({ count: 0 }),
    { name: 'my-store' } // localStorage key
  )
)

Jotai

atomWithStorage:

import { atomWithStorage } from 'jotai/utils'
const countAtom = atomWithStorage('count', 0)

Valtio

proxyWithPersist or manual:

import { proxyWithPersist } from 'valtio-yjs' // community
// Or manual: subscribe(state, () => localStorage.setItem(...))

Winner: Zustand has the most polished persist middleware.

When to Use Each

Choose Zustand When

  • You want a simple, well-documented store
  • State lives outside React (Node.js, vanilla JS)
  • You need middleware (persist, devtools, immer)
  • Team is familiar with Redux patterns (but wants less boilerplate)
  • Default choice when unsure

Choose Jotai When

  • State is highly composable (lots of derived values)
  • You want React Suspense integration
  • Fine-grained re-renders matter (large component trees)
  • You think in atoms/signals
  • Building forms or complex UI state

Choose Valtio When

  • You prefer mutable state patterns
  • Coming from Vue or MobX
  • Want the least boilerplate
  • Building prototypes quickly
  • State mutations happen outside React (event handlers, WebSocket callbacks)

FAQ

Can I use multiple libraries together?

Yes. Use Zustand for global app state, Jotai for component-level state. They don't conflict.

Which is fastest?

All three are fast enough. Performance differences are negligible for most apps. Jotai and Valtio have slightly better automatic granularity.

Do I even need a state management library?

For simple apps, React's useState and useContext are fine. These libraries shine when state is shared across many components or needs persistence/middleware.

What about Redux?

Redux Toolkit is still used in enterprise. But for new projects, Zustand covers the same patterns with 10x less boilerplate.

What about React 19 and Server Components?

All three work with client components. Server components don't need client-side state management. Use these for interactive client-side state.

Bottom Line

Zustand is the default choice for most React apps in 2026. Simple, proven, great ecosystem. Jotai is better when your state is highly composable with lots of derived values. Valtio is best when you want to just mutate objects and have the UI update.

All three are excellent. You can't go wrong. Pick based on mental model preference and move on.

Get AI tool guides in your inbox

Weekly deep-dives on the best AI coding tools, automation platforms, and productivity software.