React Zustand State Management

Zustand is a lightweight state management library for React. It provides a simple store without the boilerplate of Redux. You can set it up in minutes, and any component can read or update state without wrapping the app in a Provider. It is the fastest-growing state management option in the React ecosystem.

Why Zustand

Redux Toolkit is powerful but structured. Context API is built-in but causes re-render problems at scale. Zustand sits in the middle — simple like Context, performant like Redux, with almost zero setup.

Tool           Bundle Size   Setup Lines   Provider Needed   Re-render Opt.
--------------------------------------------------------------------------
Context API    0 KB          ~10           YES               Manual
Redux Toolkit  ~13 KB        ~40+          YES               Selectors
Zustand        ~1 KB         ~5            NO                Automatic

Installation

npm install zustand

Creating a Store

A Zustand store is a custom hook created with the create function. The store holds state and the functions to update it.

// store/counterStore.js
import { create } from "zustand";

const useCounterStore = create((set) => ({
  count: 0,                              // State
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 }),
  incrementByAmount: (amount) => set((state) => ({ count: state.count + amount })),
}));

export default useCounterStore;

Using the Store in Components

Call the store hook directly in any component. No Provider, no imports of dispatch or selectors.

import useCounterStore from "./store/counterStore";

function Counter() {
  const count = useCounterStore((state) => state.count);
  const increment = useCounterStore((state) => state.increment);
  const decrement = useCounterStore((state) => state.decrement);
  const reset = useCounterStore((state) => state.reset);

  return (
    <div>
      <h2>Count: {count}</h2>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

Selective Subscriptions Prevent Unnecessary Re-renders

Each component subscribes only to the parts of the store it uses. When other parts change, this component does not re-render.

function CartIcon() {
  // Only re-renders when items.length changes
  const itemCount = useCartStore((state) => state.items.length);
  return <span>Cart ({itemCount})</span>;
}

function CartTotal() {
  // Only re-renders when total changes
  const total = useCartStore((state) => state.total);
  return <p>Total: ${total}</p>;
}

// CartIcon does NOT re-render when total changes
// CartTotal does NOT re-render when item count changes

Real-World Example: Shopping Cart Store

// store/cartStore.js
import { create } from "zustand";

const useCartStore = create((set, get) => ({
  items: [],
  total: 0,

  addItem: (product) => {
    const currentItems = get().items;
    const existing = currentItems.find(item => item.id === product.id);

    if (existing) {
      set({
        items: currentItems.map(item =>
          item.id === product.id
            ? { ...item, quantity: item.quantity + 1 }
            : item
        ),
        total: get().total + product.price,
      });
    } else {
      set({
        items: [...currentItems, { ...product, quantity: 1 }],
        total: get().total + product.price,
      });
    }
  },

  removeItem: (productId) => {
    const item = get().items.find(i => i.id === productId);
    if (!item) return;
    set({
      items: get().items.filter(i => i.id !== productId),
      total: get().total - (item.price * item.quantity),
    });
  },

  clearCart: () => set({ items: [], total: 0 }),
}));

export default useCartStore;
// ProductCard.jsx
function ProductCard({ product }) {
  const addItem = useCartStore((state) => state.addItem);

  return (
    <div>
      <h3>{product.name}</h3>
      <p>${product.price}</p>
      <button onClick={() => addItem(product)}>Add to Cart</button>
    </div>
  );
}

Persisting State with localStorage

Zustand has a built-in persist middleware that saves and restores state from localStorage automatically.

import { create } from "zustand";
import { persist } from "zustand/middleware";

const useThemeStore = create(
  persist(
    (set) => ({
      theme: "light",
      setTheme: (theme) => set({ theme }),
    }),
    {
      name: "theme-storage", // localStorage key
    }
  )
);

// State survives page refresh automatically!
function ThemeToggle() {
  const { theme, setTheme } = useThemeStore();

  return (
    <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
      Switch to {theme === "light" ? "Dark" : "Light"} Mode
    </button>
  );
}

Accessing Store State Outside Components

Zustand lets you read and update the store outside of React components — in utility functions, API handlers, or event listeners.

import useCartStore from "./store/cartStore";

// In an API utility (not a component)
const handleCheckout = async () => {
  const { items, total, clearCart } = useCartStore.getState();

  await submitOrder({ items, total });
  clearCart(); // Directly update store without a component
};

Multiple Stores: Feature-Based Organization

Zustand encourages separate stores per domain rather than one mega-store. This keeps code organized and prevents unrelated state from triggering re-renders.

// Multiple focused stores
import useAuthStore from "./store/authStore";
import useCartStore from "./store/cartStore";
import useNotificationStore from "./store/notificationStore";

function Header() {
  const user = useAuthStore((state) => state.user);
  const cartCount = useCartStore((state) => state.items.length);
  const notifications = useNotificationStore((state) => state.unread);

  return (
    <header>
      <p>Welcome, {user?.name}</p>
      <span>Cart: {cartCount}</span>
      <span>Alerts: {notifications}</span>
    </header>
  );
}

Zustand vs Redux Toolkit: Choose Your Tool

Scenario                                         Better choice
--------------------------------------------------------------
Small to medium app with shared state            Zustand
Large enterprise app with strict patterns         Redux Toolkit
Team already knows Redux                          Redux Toolkit
You want minimal setup and less code              Zustand
Complex async flows with DevTools debugging       Redux Toolkit
React Native project                              Either
You need time-travel debugging                    Redux Toolkit

Summary

Zustand provides global state management with minimal code and no Provider wrapping required. Create stores with the create function, which takes a callback that receives set and get to define state and updater functions. Components subscribe to specific slices using a selector function, so only the subscribed data causes re-renders. The persist middleware saves state to localStorage with one line of configuration. Call useStore.getState() to access or update the store outside React components. Use multiple focused stores organized by feature to keep state modular and re-renders targeted.

Leave a Comment

Your email address will not be published. Required fields are marked *