Zustand vs Redux
State management in React has evolved from the early days of Prop Drilling to the structured world of Redux, and now to the minimalist era of Hooks-based libraries like Zustand. Understanding the trade-offs between these two giants is crucial for any React developer.
1. The Core Philosophy
Redux: The Centralized Bureaucracy
Redux is built on the principle of a Single Source of Truth. The entire application state is stored in a single object tree within a single store. To change the state, you must dispatch an action, which describes what happened, and write a reducer, which describes how the state transforms.
- Pros: Predictable, easy to debug (Redux DevTools), strict structure (good for large teams), middleware ecosystem.
- Cons: Verbose boilerplate, steep learning curve, “files jumping” (action → type → reducer → selector).
Zustand: The Agile Team
Zustand (German for “State”) takes a minimalist approach. It uses hooks to create a store. You don’t need to wrap your app in a provider. It’s unopinionated and lets you structure your state however you like.
- Pros: Almost zero boilerplate, simple API, no provider wrapper, flexible (can be centralized or atomic).
- Cons: Less structure enforced (can lead to messy code in large teams if not careful), fewer built-in patterns than Redux Toolkit.
2. Interactive: Boilerplate Visualizer
Drag the required pieces to build a simple “Counter” feature in both libraries. Notice how many pieces you need for Redux versus Zustand.
Redux Architecture
Zustand Store
Drag blocks to build the 'Counter' feature:
3. Code Comparison
Let’s implement a simple counter with increment, decrement, and reset functionality.
The Redux Way (with Redux Toolkit)
Even with Redux Toolkit (which simplifies things), there’s still a specific structure to follow.
// 1. Slice (Actions + Reducers)
import { createSlice, configureStore, PayloadAction } from '@reduxjs/toolkit';
// Define the State Interface
interface CounterState {
value: number;
}
const initialState: CounterState = { value: 0 };
const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment: (state) => { state.value += 1 },
decrement: (state) => { state.value -= 1 },
reset: (state) => { state.value = 0 },
// Payload example
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload
},
},
});
export const { increment, decrement, reset } = counterSlice.actions;
// 2. Store Configuration
export const store = configureStore({
reducer: {
counter: counterSlice.reducer,
},
});
// Types for Dispatch and State
type RootState = ReturnType<typeof store.getState>;
type AppDispatch = typeof store.dispatch;
// 3. Usage in Component
import { useSelector, useDispatch, TypedUseSelectorHook } from 'react-redux';
// Use typed hooks throughout the app
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
export function Counter() {
const count = useAppSelector((state) => state.counter.value);
const dispatch = useAppDispatch();
return (
<div>
<span>{count}</span>
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(decrement())}>-</button>
</div>
);
}
The Zustand Way
Zustand collapses the store creation, actions, and reducers into a single hook.
import { create } from 'zustand';
// 1. Create Store Hook
interface CounterState {
count: number;
increment: () => void;
decrement: () => void;
reset: () => void;
}
const useStore = create<CounterState>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
}));
// 2. Usage in Component
export function Counter() {
// Direct access to state and actions
const { count, increment, decrement } = useStore();
return (
<div>
<span>{count}</span>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
);
}
4. Architectural Diagram: Flux vs Atomic
5. When to Use Which?
| Feature | Redux (Toolkit) | Zustand |
|---|---|---|
| Boilerplate | High (Slice, Store, Provider) | Low (Just a hook) |
| Structure | Strict (Actions separate from State) | Flexible (Actions in State) |
| DevTools | Excellent (Time travel built-in) | Good (via Middleware) |
| Bundle Size | Larger | Tiny (<1kB) |
| Best For | Large teams, complex state, legacy apps | Startups, side projects, modern apps |
[!TIP] Start with Zustand. If you find yourself needing more rigid structure or complex middleware pipelines (like Sagas/Observables), you can migrate to Redux. But 95% of apps today don’t need the complexity of Redux.