DEV Community

Alex Spinov
Alex Spinov

Posted on

Zustand Has a Free API — 4KB State Management That Replaces Redux

Zustand is the most popular lightweight state management library for React — 47K+ GitHub stars, 4KB bundle, and zero boilerplate. It's the anti-Redux.

Why Zustand Over Redux?

  • 4KB vs Redux Toolkit's 35KB+
  • No Provider wrapper — just import and use
  • No boilerplate — no action types, no reducers, no dispatch
  • Works outside React — use in vanilla JS, Node.js, anywhere
  • Middleware — persist, devtools, immer, all built in
  • TypeScript native — full inference, no extra types

Quick Start

npm install zustand
Enter fullscreen mode Exit fullscreen mode
import { create } from "zustand";

// Create a store in 5 lines
const useCounterStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 }),
}));

// Use in any component — no Provider needed!
function Counter() {
  const { count, increment, decrement, reset } = useCounterStore();

  return (
    <div>
      <h1>{count}</h1>
      <button onClick={decrement}>-</button>
      <button onClick={increment}>+</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Real-World: Todo App

interface Todo {
  id: string;
  text: string;
  completed: boolean;
}

interface TodoStore {
  todos: Todo[];
  filter: "all" | "active" | "completed";
  addTodo: (text: string) => void;
  toggleTodo: (id: string) => void;
  deleteTodo: (id: string) => void;
  setFilter: (filter: TodoStore["filter"]) => void;
  filteredTodos: () => Todo[];
}

const useTodoStore = create<TodoStore>((set, get) => ({
  todos: [],
  filter: "all",

  addTodo: (text) =>
    set((state) => ({
      todos: [...state.todos, { id: crypto.randomUUID(), text, completed: false }],
    })),

  toggleTodo: (id) =>
    set((state) => ({
      todos: state.todos.map((t) =>
        t.id === id ? { ...t, completed: !t.completed } : t
      ),
    })),

  deleteTodo: (id) =>
    set((state) => ({
      todos: state.todos.filter((t) => t.id !== id),
    })),

  setFilter: (filter) => set({ filter }),

  filteredTodos: () => {
    const { todos, filter } = get();
    switch (filter) {
      case "active": return todos.filter((t) => !t.completed);
      case "completed": return todos.filter((t) => t.completed);
      default: return todos;
    }
  },
}));
Enter fullscreen mode Exit fullscreen mode

Async Actions

interface UserStore {
  users: User[];
  loading: boolean;
  error: string | null;
  fetchUsers: () => Promise<void>;
  createUser: (data: CreateUserInput) => Promise<User>;
}

const useUserStore = create<UserStore>((set) => ({
  users: [],
  loading: false,
  error: null,

  fetchUsers: async () => {
    set({ loading: true, error: null });
    try {
      const res = await fetch("/api/users");
      const users = await res.json();
      set({ users, loading: false });
    } catch (error) {
      set({ error: (error as Error).message, loading: false });
    }
  },

  createUser: async (data) => {
    const res = await fetch("/api/users", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(data),
    });
    const user = await res.json();
    set((state) => ({ users: [...state.users, user] }));
    return user;
  },
}));
Enter fullscreen mode Exit fullscreen mode

Middleware: Persist

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

const useSettingsStore = create(
  persist(
    (set) => ({
      theme: "light" as "light" | "dark",
      language: "en",
      notifications: true,
      setTheme: (theme: "light" | "dark") => set({ theme }),
      setLanguage: (language: string) => set({ language }),
      toggleNotifications: () =>
        set((state) => ({ notifications: !state.notifications })),
    }),
    {
      name: "settings-storage", // localStorage key
      partialize: (state) => ({
        theme: state.theme,
        language: state.language,
      }), // Only persist these fields
    }
  )
);
Enter fullscreen mode Exit fullscreen mode

Middleware: Immer (Mutable Syntax)

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

const useStore = create(
  immer((set) => ({
    users: [] as User[],

    addUser: (user: User) =>
      set((state) => {
        state.users.push(user); // Mutate directly!
      }),

    updateUser: (id: string, updates: Partial<User>) =>
      set((state) => {
        const user = state.users.find((u) => u.id === id);
        if (user) Object.assign(user, updates);
      }),
  }))
);
Enter fullscreen mode Exit fullscreen mode

Slices Pattern (Large Stores)

const createUserSlice = (set) => ({
  users: [],
  fetchUsers: async () => { /* ... */ },
});

const createCartSlice = (set) => ({
  items: [],
  addItem: (item) => set((state) => ({ items: [...state.items, item] })),
  total: () => 0,
});

const useStore = create((...a) => ({
  ...createUserSlice(...a),
  ...createCartSlice(...a),
}));
Enter fullscreen mode Exit fullscreen mode

Selectors (Prevent Unnecessary Rerenders)

// BAD — rerenders on ANY store change
const { count, users, theme } = useStore();

// GOOD — only rerenders when count changes
const count = useStore((state) => state.count);

// GOOD — shallow compare for objects
import { shallow } from "zustand/shallow";
const { name, email } = useStore(
  (state) => ({ name: state.name, email: state.email }),
  shallow
);
Enter fullscreen mode Exit fullscreen mode

Zustand vs Redux vs Jotai vs Valtio

Feature Zustand Redux Toolkit Jotai Valtio
Size 4KB 35KB+ 8KB 6KB
Boilerplate Minimal Medium Minimal Minimal
Provider No Yes Yes No
DevTools Plugin Built-in Plugin Plugin
Persist Middleware Manual Plugin Plugin
Learning curve Low Medium Low Low
Best for Most apps Large teams Atomic state Proxy-based

Need to scrape data from any website and get it in structured JSON? Check out my web scraping tools on Apify — no coding required, results in minutes.

Have a custom data extraction project? Email me at spinov001@gmail.com — I build tailored scraping solutions for businesses.

Top comments (0)