DEV Community

Cover image for 🧠 Mastering Zustand — The Modern React State Manager (v4 & v5 Guide)
Vishwark
Vishwark

Posted on

🧠 Mastering Zustand — The Modern React State Manager (v4 & v5 Guide)

Zustand has quickly become one of the most loved state-management libraries for React.

It’s tiny, fast, scalable, and framework-agnostic — and with the new v4+ and v5 releases, it’s more powerful than ever.

In this post, we’ll go from basics to advanced — covering setup, selectors, async actions, middlewares, and best practices for production apps.


🪄 What is Zustand?

“A small, fast, and scalable bear-bones state-management solution.”

It gives you global state with a minimal API — using React hooks, no providers, and no boilerplate.

import { create } from 'zustand';

const useBearStore = create(set => ({
  bears: 0,
  increase: () => set(state => ({ bears: state.bears + 1 })),
}));
Enter fullscreen mode Exit fullscreen mode

Simple, right? Let’s see how to use it efficiently.


⚙️ Installation & Setup

npm install zustand
Enter fullscreen mode Exit fullscreen mode

Import:

import { create } from 'zustand';
Enter fullscreen mode Exit fullscreen mode

v4+ Change:

Default exports are gone — always use { create }.

Middlewares (optional):

npm install zustand/middleware
Enter fullscreen mode Exit fullscreen mode

🧩 Creating Your First Store

const useBearStore = create(set => ({
  bears: 0,
  increase: () => set(state => ({ bears: state.bears + 1 })),
  removeAll: () => set({ bears: 0 }),
}));
Enter fullscreen mode Exit fullscreen mode

Then in React:

const bears = useBearStore(state => state.bears);
Enter fullscreen mode Exit fullscreen mode

✅ Zustand subscribes only to what you select — super efficient.


🎯 Selectors and Optimization

Bad ❌:

const { bears } = useBearStore(); // subscribes to full store
Enter fullscreen mode Exit fullscreen mode

Good ✅:

const bears = useBearStore(s => s.bears);
Enter fullscreen mode Exit fullscreen mode

Multiple keys? Use shallow:

import { shallow } from 'zustand/shallow';

const { bears, increase } = useBearStore(
  s => ({ bears: s.bears, increase: s.increase }),
  shallow
);
Enter fullscreen mode Exit fullscreen mode

✅ Only re-renders when bears or increase change.


⚡ Async Actions

Just use async/await inside your store!

const useBearStore = create(set => ({
  bears: 0,
  loading: false,
  fetchBears: async () => {
    set({ loading: true });
    const data = await fetch('/api/bears').then(r => r.json());
    set({ bears: data.length, loading: false });
  },
}));
Enter fullscreen mode Exit fullscreen mode

No thunks, no sagas — plain JS.


🧠 Middlewares

Zustand supports composable enhancers like Redux — but with much less boilerplate.

🔹 Devtools — Time Travel & Debugging

Adds Redux DevTools integration so you can inspect, time-travel, and debug store updates visually.

import { devtools } from 'zustand/middleware';

const useStore = create(
  devtools(set => ({
    bears: 0,
    increase: () => set(s => ({ bears: s.bears + 1 })),
  }))
);
Enter fullscreen mode Exit fullscreen mode

🔹 Persist — Save & Rehydrate State

Automatically syncs store data to localStorage, sessionStorage, or custom async storage to survive page reloads.

import { persist } from 'zustand/middleware';

const usePersistedStore = create(
  persist(
    set => ({
      bears: 0,
      increase: () => set(s => ({ bears: s.bears + 1 })),
    }),
    { name: 'bear-storage' }
  )
);
Enter fullscreen mode Exit fullscreen mode

🔹 Immer — Simplify Immutable Updates

Lets you write mutable logic (state.count++) safely, while Immer handles immutability under the hood.

import { immer } from 'zustand/middleware/immer';

const useStore = create(
  immer(set => ({
    bears: 0,
    increase: () =>
      set(state => {
        state.bears += 1;
      }),
  }))
);
Enter fullscreen mode Exit fullscreen mode

You can also compose multiple middlewares:

create(devtools(persist(immer(fn), { name: 'store' })));
Enter fullscreen mode Exit fullscreen mode

📘 Recommended order: immer → persist → devtools


💾 Persistent State — Real-World Example

Here’s a production-ready setup with multiple options:

import { create } from "zustand";
import { devtools, persist, createJSONStorage } from "zustand/middleware";

interface AppState {
  user: { id: number; name: string } | null;
  theme: "light" | "dark";
  notifications: number;
  setUser: (user: AppState["user"]) => void;
  toggleTheme: () => void;
  addNotification: () => void;
  clearNotifications: () => void;
}

export const useAppStore = create<AppState>()(
  devtools(
    persist(
      (set) => ({
        user: null,
        theme: "light",
        notifications: 0,
        setUser: (user) => set({ user }),
        toggleTheme: () => set((s) => ({ theme: s.theme === "light" ? "dark" : "light" })),
        addNotification: () => set((s) => ({ notifications: s.notifications + 1 })),
        clearNotifications: () => set({ notifications: 0 }),
      }),
      {
        name: "app-storage",
        version: 2,
        partialize: (s) => ({ theme: s.theme, notifications: s.notifications }),
        migrate: (state, version) => (version === 1 ? { ...state, notifications: 0 } : state),
        storage: createJSONStorage(() => localStorage),
      }
    ),
    { name: "AppStore" }
  )
);
Enter fullscreen mode Exit fullscreen mode

✅ Features:

  • 🧭 DevTools ready – Inspect & time-travel easily
  • 💾 Versioned migrations – Handle schema changes gracefully
  • 🧱 Partial persistence – Save only necessary slices
  • ⚙️ Custom storage – Use localStorage, IndexedDB, or async storages

🧱 Vanilla Stores (Non-React)

Zustand works outside React too — perfect for Node or testing.

import { createStore } from "zustand/vanilla";

const bearStore = createStore((set) => ({
  bears: 0,
  increase: () => set((s) => ({ bears: s.bears + 1 })),
}));
Enter fullscreen mode Exit fullscreen mode

You can use it in React via:

import { useStore } from "zustand";
const useBearStore = (selector) => useStore(bearStore, selector);
Enter fullscreen mode Exit fullscreen mode

✅ Clean separation between UI and logic.


🧩 Best Practices

✅ Do ❌ Don’t
Use selectors for each field Use useStore() without selector
Split stores by domain Put everything in one giant store
Persist only what’s needed Persist tokens or sensitive data
Use shallow for multi-keys Return new objects each render
Compose middlewares properly Mix order randomly
Write tests via .getState() Mock React unnecessarily

🧠 Advanced Patterns

Cross-Store Sync

useUserStore.subscribe((s) => {
  if (!s.user) useThemeStore.setState({ theme: "light" });
});
Enter fullscreen mode Exit fullscreen mode

Derived State

const useCartStore = create((set, get) => ({
  items: [],
  get total() {
    return get().items.reduce((sum, i) => sum + i.price, 0);
  },
}));
Enter fullscreen mode Exit fullscreen mode

Subscriptions

useCartStore.subscribe(
  (s) => s.items,
  (items) => console.log("Cart changed", items)
);
Enter fullscreen mode Exit fullscreen mode

⚙️ Migrating from Older Versions

Change Old New
Imports import create from 'zustand' import { create } from 'zustand'
TypeScript stores create<State>(fn) create<State>()(fn)
Persist API Basic localStorage only Supports custom storage, versioning
Vanilla API Unstable Official createStore()
Devtools Single param Supports name, async-safe

🧭 When to Use Zustand

✅ Ideal for:

  • Apps needing shared state without Redux overhead
  • React Native or hybrid frameworks
  • SSR & non-React logic (via vanilla stores)
  • Projects that value simplicity + performance

✨ Key Takeaways

  • Zustand is minimal but powerful.
  • Uses selectors for performance.
  • Supports async, middlewares, persistence, and vanilla mode.
  • Fully typed for TypeScript.
  • No Providers. No boilerplate. Just React + hooks.

🧡 Final Thought

Zustand hits the perfect sweet spot —

“as simple as useState, as capable as Redux.”

It’s not just for small apps — it’s the React state library you’ll actually enjoy maintaining.


💬 What’s your favorite Zustand trick or middleware combo?

Drop it in the comments — let’s trade some bear stories 🐻

Top comments (0)