Skip to main content
Zustand is one of many state management libraries for React. This guide compares Zustand with popular alternatives to help you understand when and why to choose each one.

At a glance

FeatureZustandReduxContext APIJotaiValtioRecoil
Bundle size~1KB~3KB (RTK)0KB (built-in)~3KB~3KB~14KB
BoilerplateMinimalMediumLowMinimalMinimalMedium
Providers neededNoYesYesYesNoYes
State modelImmutableImmutableAnyImmutableMutableImmutable
SelectorsManualManualManualAutomaticAutomaticAutomatic
DevToolsVia middlewareBuilt-inNoVia toolsVia toolsBuilt-in
TypeScriptExcellentExcellentGoodExcellentExcellentGood
Learning curveLowMedium-HighLowMediumLowMedium

Zustand vs Redux

Overview

Both Zustand and Redux are based on immutable state models with unidirectional data flow. However, they differ significantly in API design and requirements.

Key differences

No providers

Zustand doesn’t require context providers, while Redux needs <Provider> wrapping your app.

Less boilerplate

Zustand needs just one function call. Redux requires actions, reducers, and action creators.

Hooks-first

Zustand is built on hooks. Redux added hooks later to complement connect().

Smaller bundle

Zustand is ~1KB, Redux Toolkit is ~3KB.

Code comparison

import { create } from 'zustand'

type State = {
  count: number
}

type Actions = {
  increment: (qty: number) => void
  decrement: (qty: number) => void
}

const useCountStore = create<State & Actions>((set) => ({
  count: 0,
  increment: (qty: number) => set((state) => ({ count: state.count + qty })),
  decrement: (qty: number) => set((state) => ({ count: state.count - qty })),
}))

// Use anywhere, no provider needed
function Counter() {
  const count = useCountStore((state) => state.count)
  const increment = useCountStore((state) => state.increment)
  return <button onClick={() => increment(1)}>{count}</button>
}

When to use Redux over Zustand

Choose Redux if you:
  • Need comprehensive DevTools out of the box
  • Want strict architectural patterns enforced
  • Have a team familiar with Redux patterns
  • Need extensive middleware ecosystem
  • Prefer separation of actions and reducers

Zustand vs Context API

Overview

React’s Context API is built-in and works well for simple state sharing. Zustand is a dedicated state management library with more features.

Key differences

Pros:
  • Built into React, no dependencies
  • Simple for small-scale state sharing
  • Natural React patterns
Cons:
  • All consumers re-render when context changes
  • Requires provider boilerplate
  • No built-in selector optimization
  • Verbose for complex state

Code comparison

import { createContext, useContext, useState } from 'react'

const CountContext = createContext(null)

function CountProvider({ children }) {
  const [count, setCount] = useState(0)
  const increment = () => setCount(c => c + 1)
  
  return (
    <CountContext.Provider value={{ count, increment }}>
      {children}
    </CountContext.Provider>
  )
}

function Counter() {
  const { count, increment } = useContext(CountContext)
  return <button onClick={increment}>{count}</button>
}

// Need to wrap app
function App() {
  return (
    <CountProvider>
      <Counter />
    </CountProvider>
  )
}
Context API performance gotcha: All components using a context re-render when any part of the context value changes, even if they only use a small piece of it.

Zustand vs Jotai

Overview

Both are from the Poimandres collective. Zustand uses a single store approach, while Jotai uses atomic state.

State model differences

import { create } from 'zustand'

type State = {
  count: number
  name: string
}

type Actions = {
  updateCount: (fn: (count: number) => number) => void
  updateName: (name: string) => void
}

const useStore = create<State & Actions>((set) => ({
  count: 0,
  name: 'John',
  updateCount: (fn) => set((state) => ({ count: fn(state.count) })),
  updateName: (name) => set({ name }),
}))

// All state in one place

When to choose each

Choose Zustand if you:
  • Prefer a single source of truth
  • Want to define all state and actions together
  • Like Redux-style centralized state
  • Need to access state outside React easily
Choose Jotai if you:
  • Prefer atomic, composable state
  • Want automatic derived state
  • Like bottom-up state composition
  • Need fine-grained dependency tracking

Zustand vs Valtio

Overview

Valtio and Zustand are both from Poimandres, but use fundamentally different state models.

Immutable vs mutable

import { create } from 'zustand'

type State = {
  obj: { count: number }
}

const useStore = create<State>((set) => ({
  obj: { count: 0 }
}))

// Update immutably
useStore.setState((prev) => ({
  obj: { count: prev.obj.count + 1 }
}))

Render optimization

Zustand requires manual selectors for optimization, while Valtio automatically tracks property access.
Choose Zustand if you:
  • Prefer immutable updates
  • Want explicit control over re-renders
  • Like Redux-style patterns
Choose Valtio if you:
  • Prefer mutable updates
  • Want automatic render optimization
  • Like Vue/MobX-style reactivity

Zustand vs Recoil

Overview

Recoil is Facebook’s experimental state management library using atomic state with string keys.

Key differences

Atoms

Recoil uses atoms with string keys, Zustand uses a single store or vanilla stores.

Providers

Recoil requires <RecoilRoot>, Zustand doesn’t need providers.

Experimental

Recoil is experimental, Zustand is production-ready.

Bundle size

Zustand ~1KB, Recoil ~14KB.

Code comparison

import { create } from 'zustand'

type State = {
  count: number
}

type Actions = {
  setCount: (fn: (count: number) => number) => void
}

const useCountStore = create<State & Actions>((set) => ({
  count: 0,
  setCount: (fn) => set((state) => ({ count: fn(state.count) })),
}))

function Component() {
  const count = useCountStore((state) => state.count)
  const setCount = useCountStore((state) => state.setCount)
  // ...
}

Migration guides

Switching state management libraries? Here are quick migration tips:
  1. Replace action creators with direct set calls
  2. Combine reducers into a single store
  3. Remove the Provider wrapper
  4. Replace useSelector with store selectors
  5. Replace useDispatch with direct action calls
// Before (Redux)
const count = useSelector(state => state.count)
const dispatch = useDispatch()
dispatch(increment())

// After (Zustand)
const count = useStore(state => state.count)
const increment = useStore(state => state.increment)
increment()
  1. Replace createContext with create
  2. Move state and setters into the store
  3. Remove Provider components
  4. Replace useContext with store selectors
// Before (Context)
const { count, increment } = useContext(CountContext)

// After (Zustand)
const count = useStore(state => state.count)
const increment = useStore(state => state.increment)

Choosing the right tool

The best state management solution depends on your specific needs, team preferences, and project constraints. Here’s a quick decision guide:
Start with Zustand if:
  • You want minimal boilerplate
  • You’re building a new project
  • You value simplicity and small bundle size
  • You don’t need atomic state
Use Redux if:
  • You have an existing Redux codebase
  • You need time-travel debugging heavily
  • Your team is deeply familiar with Redux
  • You want strict architectural patterns
Use Context API if:
  • You have simple, infrequent state updates
  • You want zero dependencies
  • Performance isn’t critical
  • You only need theme or auth state
Try Jotai if:
  • You like atomic state composition
  • You want automatic derived state
  • You need fine-grained subscriptions
  • You prefer bottom-up architecture
Try Valtio if:
  • You prefer mutable updates
  • You want automatic tracking
  • You like Vue or MobX patterns
  • You want minimal syntax

Further resources

npm download trends

Compare popularity and trends

Bundle size comparison

Check actual bundle impact

Build docs developers (and LLMs) love