Zustand: Lightweight React State Management
Zustand is a lightweight state management library (2.9 KB minified + gzipped) that lets you create global React stores without reducers, Context providers, or boilerplate. It uses a simple subscription model where components re-render only when the specific state they reference changes. Unlike Context, which forces all consumers to re-render when the provider updates, Zustand uses direct subscriptions — more like event emitters than React's component tree.
I adopted Zustand in 2024 after managing five production React applications with different state patterns. The library's simplicity and predictable re-rendering made debugging significantly faster compared to reducer-heavy solutions.
How Zustand Works: The Store Subscription Model
Zustand's core is the create() function, which builds a store from a single function that returns state and mutation methods. Unlike Redux, there is no action-type dispatch system; you define methods directly on the store. When state updates, Zustand notifies only the components that depend on the changed fields.
Here is the simplest Zustand store:
import { create } from 'zustand';
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
}));
The set function is the mechanism for updating state. Zustand merges the object you pass into set() with the existing state, similar to useState() in React. The function parameter (state) => ... gives you access to the current state, so you can compute new values based on old ones (like incrementing a counter).
To use this store in a component, call the hook like any React hook:
export function Counter() {
const count = useStore((state) => state.count);
const increment = useStore((state) => state.increment);
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+1</button>
</div>
);
}
The selector function (state) => state.count ensures the component only re-renders when count changes. If another part of the store (say, a user profile) updates, this component ignores that change.
Subscriptions and Manual Updates
Zustand stores are not React-only. The underlying store is a plain JavaScript object with a subscription API. You can read state and listen to changes outside React:
// Get current state synchronously
const currentCount = useStore.getState().count;
// Subscribe to all changes
const unsubscribe = useStore.subscribe(
(state) => state.count,
(count) => console.log('Count changed:', count)
);
// Later, unsubscribe
unsubscribe();
This is powerful for logging, persisting to localStorage, or triggering side effects outside React components. For example, if you need to save state whenever it changes, you can subscribe once in your app root:
useStore.subscribe(
(state) => state.count,
(count) => localStorage.setItem('count', count)
);
Key Takeaways
- Zustand provides a minimal, subscription-based store with no boilerplate — define state and actions in one function.
- The selector pattern
(state) => state.fieldensures fine-grained re-renders; only components using changed fields re-render. set()merges updates; pass a function to compute new state from old state (immutable pattern).getState()reads state synchronously outside components;subscribe()listens to changes without React.- At 2.9 KB, Zustand adds almost no bundle size to your app.
Frequently Asked Questions
Does Zustand replace Redux?
Zustand is simpler than Redux for most use cases. Redux shines when you need time-travel debugging, complex action chains, or a team unfamiliar with functional state updates. Zustand is ideal for managing user preferences, UI state, and small-to-medium data. For 90% of modern React apps, Zustand is faster to write and easier to debug.
Can I use Zustand with TypeScript?
Yes. Zustand has excellent TypeScript support. Define a type for your store and pass it to create(): create<StoreType>((set) => ({ ... })). TypeScript will type-check your state, setters, and selector functions.
How do I persist Zustand state?
Zustand includes a persist middleware: create(persist((set) => ({ ... }), { name: 'store' })). This automatically syncs state with localStorage. You can also manually subscribe and save to any backend.
Is Zustand "too simple"?
Zustand scales well. For complex async logic, you can add middleware (e.g., immer for immutable updates or custom logging). Many production apps (Vercel, Stripe) use Zustand at scale. Simplicity is a feature, not a limitation.