Back to blog
Web DevelopmentReactFrontend

State management in React with Zustand: a lightweight alternative

·3 min read

State management in React has evolved significantly. While Redux remains popular and Context API handles simple cases, Zustand has emerged as a compelling middle ground — minimal boilerplate, excellent TypeScript support, and powerful enough for complex applications.

This guide explores why Zustand is worth your attention and how to use it effectively.

The problem with state management

React's built-in state management works well for local component state. But as applications grow, you need shared state — data that multiple components need to access and update.

Context API solves this but has performance issues. Every consumer re-renders when any context value changes, regardless of whether their specific slice of data changed.

Redux is powerful but verbose. Actions, reducers, dispatch, connect, selectors — the boilerplate adds up. Newcomers face a steep learning curve.

Zustand offers a simpler model: a bare-bones store with hooks-based access.

Creating a Zustand store

A Zustand store is a plain function that returns an object. No providers, no reducers, no action types:

import { create } from "zustand";

interface CartStore {
  items: CartItem[];
  addItem: (item: CartItem) => void;
  removeItem: (id: string) => void;
  total: () => number;
}

const useCartStore = create<CartStore>((set, get) => ({
  items: [],
  addItem: (item) => set((state) => ({ items: [...state.items, item] })),
  removeItem: (id) => set((state) => ({
    items: state.items.filter((i) => i.id !== id),
  })),
  total: () => get().items.reduce((sum, i) => sum + i.price, 0),
}));

That's the entire store. No provider wrapping your app, no action constants, no dispatch.

Using the store in components

Zustand stores are React hooks. Components subscribe to the store and get the data they need:

function CartSummary() {
  const items = useCartStore((state) => state.items);
  const total = useCartStore((state) => state.total());

  return (
    <div>
      <p>{items.length} items in cart</p>
      <p>Total: ${total}</p>
    </div>
  );
}

The selector function ((state) => state.items) controls re-renders. Components only re-render when their selected slice changes — avoiding the Context API's re-render problem.

Middleware and advanced patterns

Zustand supports middleware for common needs:

  • persist: save and restore state from localStorage or AsyncStorage
  • immer: use mutable-style state updates with Immer
  • devtools: connect to Redux DevTools for debugging
  • subscribeWithSelector: fine-grained subscriptions outside React components
import { persist } from "zustand/middleware";

const useStore = create(
  persist(
    (set) => ({
      theme: "light",
      setTheme: (theme) => set({ theme }),
    }),
    { name: "app-settings" }
  )
);

Zustand vs alternatives

| Feature | Zustand | Redux Toolkit | Context API | |---------|---------|---------------|-------------| | Boilerplate | Minimal | Moderate | Minimal | | Performance | Excellent (selectors) | Excellent | Poor (re-renders) | | TypeScript | Built-in | Good | Good | | Bundle size | ~1KB | ~11KB | 0 (built-in) | | DevTools | Via middleware | Built-in | Manual |

When to use Zustand

Zustand is ideal for medium-complexity applications where Context API falls short but Redux feels like overkill. Shopping carts, user preferences, notification systems, and real-time data feeds are natural fits.

For very simple cases, local state or Context is sufficient. For massive enterprise applications with complex state logic, Redux Toolkit's structure and middleware ecosystem may be preferable.


State management doesn't have to be complicated. Zustand proves that a simple, well-designed API can handle complex requirements without the ceremony.

Building a React application and choosing your state management approach? At Vynta use Zustand, Redux Toolkit, or Context depending on the project's needs — always picking the right tool for the job.

Have a project in mind?

Let's talk