Redux needs actions, reducers, action creators, selectors, middleware, and a Provider wrapper. Context API re-renders every consumer when any value changes. You just want shared state that works.
What if global state was a single function call? No Provider. No reducer. No boilerplate.
That's Zustand. 1KB, no dependencies, works with React out of the box.
The Simplest Store
import { create } from "zustand";
interface CounterStore {
count: number;
increment: () => void;
decrement: () => void;
reset: () => void;
}
const useCounter = create<CounterStore>((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 = useCounter((state) => state.count);
const increment = useCounter((state) => state.increment);
return <button onClick={increment}>Count: {count}</button>;
}
That's the entire setup. No <Provider>, no createStore(), no combineReducers().
Real-World Store: Shopping Cart
interface CartItem {
id: string;
name: string;
price: number;
quantity: number;
}
interface CartStore {
items: CartItem[];
addItem: (item: Omit<CartItem, "quantity">) => void;
removeItem: (id: string) => void;
updateQuantity: (id: string, quantity: number) => void;
total: () => number;
clearCart: () => void;
}
const useCart = create<CartStore>((set, get) => ({
items: [],
addItem: (item) => set((state) => {
const existing = state.items.find((i) => i.id === item.id);
if (existing) {
return {
items: state.items.map((i) =>
i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i
),
};
}
return { items: [...state.items, { ...item, quantity: 1 }] };
}),
removeItem: (id) => set((state) => ({
items: state.items.filter((i) => i.id !== id),
})),
updateQuantity: (id, quantity) => set((state) => ({
items: state.items.map((i) => (i.id === id ? { ...i, quantity } : i)),
})),
total: () => get().items.reduce((sum, item) => sum + item.price * item.quantity, 0),
clearCart: () => set({ items: [] }),
}));
Middleware: Persist, DevTools, Immer
import { create } from "zustand";
import { persist, devtools } from "zustand/middleware";
import { immer } from "zustand/middleware/immer";
const useStore = create<StoreState>()(
devtools(
persist(
immer((set) => ({
todos: [],
addTodo: (text: string) => set((state) => {
// Immer lets you "mutate" — it creates immutable updates
state.todos.push({ id: crypto.randomUUID(), text, done: false });
}),
toggleTodo: (id: string) => set((state) => {
const todo = state.todos.find((t) => t.id === id);
if (todo) todo.done = !todo.done;
}),
})),
{ name: "todo-storage" } // Persists to localStorage
),
{ name: "TodoStore" } // Shows in Redux DevTools
)
);
Selective Rendering — Only Re-Render What Changes
// ✅ Only re-renders when count changes
const count = useStore((state) => state.count);
// ✅ Only re-renders when user.name changes
const name = useStore((state) => state.user.name);
// ❌ Re-renders on ANY state change (avoid this)
const everything = useStore();
Zustand vs Redux vs Context vs Jotai
| Feature | Zustand | Redux Toolkit | Context API | Jotai |
|---|---|---|---|---|
| Bundle size | 1 KB | 11 KB | 0 (built-in) | 2 KB |
| Boilerplate | Minimal | Moderate | Low | Minimal |
| Provider needed | No | Yes | Yes | Yes |
| DevTools | Via middleware | Built-in | No | Via plugin |
| Persistence | Via middleware | Manual | Manual | Via plugin |
| Re-render control | Selector-based | Selector-based | Poor | Atom-based |
| Learning curve | 5 minutes | 30 minutes | 10 minutes | 10 minutes |
When to Choose Zustand
Choose Zustand when:
- You want the simplest possible global state
- No Provider wrapper is a priority (micro-frontends, libraries)
- You need selectors to prevent unnecessary re-renders
- Your state logic is straightforward (not deeply nested atoms)
Skip Zustand when:
- You need atomic state (individual pieces of state) → Jotai
- You need extensive middleware ecosystem → Redux Toolkit
- Your state is component-local → just use useState
The Bottom Line
Zustand is state management reduced to its essence. Create a store, use it in components, done. No ceremony, no boilerplate, no architecture debates.
Start here: zustand.docs.pmnd.rs
Need custom data extraction, scraping, or automation? I build tools that collect and process data at scale — 78 actors on Apify Store and 265+ open-source repos. Email me: Spinov001@gmail.com | My Apify Actors
Top comments (0)