Skip to content
Created: September 30, 2025

These examples use Zustand 5. The Hook returned by create does not accept a second custom equality argument; use useShallow to stabilize object selector results.

Zustand is a lightweight, boilerplate-free React state management library.It is simpler than Redux, more efficient than Context, and is very suitable for small to medium-sized projects.

1. Zustand core concept ​

✅ Design philosophy ​

  • Minimalist API
  • Based on Hooks
  • No Provider (default)
  • Support middleware
  • Support TS friendly

Core idea:

Use a global store + hook to selectively subscribe to state

2. Basic introduction ​

1️⃣ Installation ​

bash
npm install zustand@5

2️⃣ Create the first Store ​

ts
import { create } from 'zustand'

const useCounterStore = create((set) => ({
  count: 0,
  increase: () => set((state) => ({ count: state.count + 1 })),
  decrease: () => set((state) => ({ count: state.count - 1 })),
}))

3️⃣ Used in components ​

tsx
function Counter() {
  const { count, increase, decrease } = useCounterStore()

  return (
    <>
      <div>{count}</div>
      <button onClick={increase}>+</button>
      <button onClick={decrease}>-</button>
    </>
  )
}
  • ✔ Automatic subscription
  • ✔ Automatically unsubscribe when components are uninstalled
  • ✔ No Provider

3. Advanced core skills ​

🎯 1. Accurate subscription (avoiding invalid rendering) ​

ts
const count = useCounterStore((state) => state.count)

Only listens to count, changes in other fields will not trigger re-rendering.

Matching and comparison ​

ts
import { useShallow } from 'zustand/react/shallow'

const { count, increase } = useCounterStore(
  useShallow((state) => ({ count: state.count, increase: state.increase }))
)

🎯 2. Asynchronous operation (request interface) ​

ts
const useUserStore = create((set) => ({
  user: null,
  loading: false,
  fetchUser: async () => {
    set({ loading: true })
    const res = await fetch('/api/user')
    const data = await res.json()
    set({ user: data, loading: false })
  }
}))
  • ✔ Zustand does not limit async
  • ✔ No thunk / saga concept

🎯 3. Middleware usage ​

persist (local storage) ​

ts
import { persist } from 'zustand/middleware'

const useStore = create(
  persist(
    (set) => ({
      theme: 'light',
      toggle: () => set((s) => ({ theme: s.theme === 'light' ? 'dark' : 'light' })),
    }),
    {
      name: 'app-storage',
    }
  )
)

devtools (debugging) ​

ts
import { devtools } from 'zustand/middleware'

const useStore = create(
  devtools((set) => ({
    count: 0,
    inc: () => set((s) => ({ count: s.count + 1 }), false, 'inc')
  }))
)

Can be viewed in Redux DevTools.

🎯 4. Split modules (large projects) ​

Recommended structure:

store/
 ├── index.ts
 ├── user.ts
 ├── cart.ts
 └── theme.ts

Module merge ​

ts
import { create } from 'zustand'
import { createUserSlice } from './user'
import { createCartSlice } from './cart'

export const useStore = create((...a) => ({
  ...createUserSlice(...a),
  ...createCartSlice(...a),
}))
  • ✔ Similar to Redux slice
  • ✔ Clearer
  • ✔ High maintainability

4. Advanced practical skills ​

🔥 1. get() reads the latest status ​

ts
const useStore = create((set, get) => ({
  count: 0,
  double: () => {
    const current = get().count
    set({ count: current * 2 })
  }
}))

🔥 2. subscribe monitoring (non-component environment) ​

ts
const unsubscribe = useStore.subscribe((state, previous) => {
  if (state.count !== previous.count) {
    console.log('count changed:', state.count)
  }
})
// Call unsubscribe() when the listener is no longer needed.

Applies to:

  • Logging system
  • websocket
  • External module listening

🔥 3. immer middleware (complex object) ​

ts
// Install the optional dependency first: npm install immer
import { immer } from 'zustand/middleware/immer'

const useStore = create(
  immer((set) => ({
    user: { name: '', age: 0 },
    updateName: (name: string) =>
      set((state) => {
        state.user.name = name
      }),
  }))
)
  • ✔ Variable writing
  • ✔ Internally automatic immutable

🔥 4. Reset Store ​

ts
const initialState = { count: 0 }

const useStore = create((set) => ({
  ...initialState,
  reset: () => set(initialState),
}))

5. Summary of performance optimization ​

✅Must do ​

  • Use selector
  • Use shallow
  • split store
  • Avoid returning large objects directly

❌Avoid ​

  • Deconstruction of the entire state
  • Write complex UI states in the store
  • Put all states in one store

6. Zustand vs Redux comparison ​

ComparisonZustandRedux
Learning costLowHigh
Template codeLessMore
MiddlewareSupportStrong
Suitable projectsSmall/medium/medium-largeMedium-large

7. Enterprise-level best practices ​

1️⃣ Status layering ​

  • UI state (local)
  • Global shared state (Zustand)
  • Server status (React Query)

Recommended combination:

  • Zustand + React Query
  • Zustand + Vite
  • Zustand + Next.js

2️⃣ Don’t abuse global state ​

Judgment criteria:

  • Sharing multiple components?
  • Does it span pages?
  • Is caching required?

Otherwise use useState.

8. Summary of common pitfalls ​

  • ⚠ Initialized twice under StrictMode
  • ⚠ persist needs to handle version upgrades
  • ⚠ SSR needs to avoid state pollution
  • ⚠ Pay attention to coverage issues with asynchronous concurrency

9. Examples of actual project structures ​

src/
 ├── store/
 │   ├── authStore.ts
 │   ├── productStore.ts
 │   └── index.ts
 ├── hooks/
 ├── pages/
 └── services/

🔟 Ultimate summary ​

Zustand is suitable for:

  • ✔ Pursue simplicity
  • ✔ Don’t want to write reducer
  • ✔ Don’t want to configure complex middleware
  • ✔ Want the ultimate development experience

One sentence summary:

Small but beautiful, sufficient and powerful.