This article was originally published on AI Study Room. For the full version with working code examples and related articles, visit the original post.
Introduction
React state management has evolved significantly. Redux, once the default choice for complex applications, now competes with simpler alternatives like Zustand and Jotai. Each takes a different approach: Redux centralizes state with strict patterns, Zustand provides a minimal store with hooks, and Jotai offers atomic, Recoil-inspired state management. This comparison helps you choose the right state management approach.
Redux Toolkit
Redux Toolkit (RTK) is Redux's modern incarnation, eliminating much of the boilerplate that plagued classic Redux.
Philosophy: Single centralized store with predictable updates through reducers and actions.
import { createSlice, configureStore } from "@reduxjs/toolkit";
const counterSlice = createSlice({
name: "counter",
initialState: { value: 0 },
reducers: {
increment: (state) => { state.value += 1; },
incrementBy: (state, action) => { state.value += action.payload; },
},
});
const store = configureStore({
reducer: { counter: counterSlice.reducer },
});
export const { increment, incrementBy } = counterSlice.actions;
export type RootState = ReturnType<typeof store.getState>;
// In component:
function Counter() {
const count = useSelector((state: RootState) => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<p>{count}</p>
<button onClick={() => dispatch(increment())}>+1</button>
</div>
);
}
Strengths:
- Mature ecosystem with middleware (Redux Thunk, Redux Saga)
- DevTools with time-travel debugging
- TypeScript integration is excellent with RTK
- Clear patterns for large teams
- Well-documented for complex async flows
Weaknesses:
- Conceptual overhead (actions, reducers, dispatch, selectors)
- More boilerplate than alternatives (even with RTK)
- Selectors need memoization for performance
- Centralized store can become a bottleneck in very large apps
Zustand
Zustand provides a minimal store with React hooks, no providers needed.
Philosophy: A small, fast, and scalable state management solution with no boilerplate.
import { create } from "zustand";
interface CounterState {
count: number;
increment: () => void;
incrementBy: (amount: number) => void;
}
const useCounterStore = create<CounterState>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
incrementBy: (amount) => set((state) => ({ count: state.count + amount })),
}));
// In component — no Provider needed
function Counter() {
const count = useCounterStore((state) => state.count);
const increment = useCounterStore((state) => state.increment);
return (
<div>
<p>{count}</p>
<button onClick={increment}>+1</button>
</div>
);
}
Strengths:
- Minimal boilerplate — create a store, use it
- No Provider wrapping needed
- Subscription-based rendering (only re-renders on accessed state)
- Simple async actions (no middleware needed)
- Tiny bundle size (~1KB)
- Works outside React (vanilla JS stores)
Weaknesses:
- Fewer middleware options than Redux
- Less structure for large teams (can lead to inconsistent patterns)
- DevTools require additional setup
- No built-in data fetching or caching (unlike RTK Query or TanStack Query)
Jotai
Jotai takes an atomic approach — each piece of state is an independent atom.
Philosophy: Build state by composing primitive atoms, much like React's useState but shared.
import { atom, useAtom } from "jotai";
// Primitive atom
const countAtom = atom(0);
// Derived atom (computed state)
const doubleCountAtom = atom((get) => get(countAtom) * 2);
// Async atom
const userAtom = atom(async () => {
const response = await fetch("/api/user");
return response.json();
});
// In component
function Counter() {
const [count, setCount] = useAtom(countAtom);
const [doubleCount] = useAtom(doubleCountAtom);
return (
<div>
<p>Count: {count}</p>
<p>Double: {doubleCount}</p>
<button onClick={() => setCount((c) => c + 1)}>+1</button>
</div>
);
}
Strengths:
- Granular re-renders (only components using a specific atom re-render)
- Built-in async atoms (no middleware for async)
- Composable atoms for derived state
- Tiny bundle size (~3KB)
- No global store or Provider needed
Weaknesses:
- Less mature ecosystem than Redux
- Atomic approach can be unfamiliar to new team members
- Debugging can be harder with many small atoms
- SSR requires additional configuration
Comparison Table
| Aspect | Redux Toolkit | Zustand | Jotai |
|---|---|---|---|
| Architecture | Centralized store | Multiple stores | Atomic atoms |
| Boilerplate | Moderate | Minimal | Minimal |
| Bundle size | ~12KB | ~1KB | ~3KB |
| Learning curve | Steep | Gentle | Moderate |
| DevTools | Built-in | Add-on | Add-on |
| Middleware | Rich ecosystem | Immer, persist | None needed |
| Async support | Thunks/Sagas | Native in |
Read the full article on AI Study Room for complete code examples, comparison tables, and related resources.
Found this useful? Check out more developer guides and tool comparisons on AI Study Room.
Top comments (0)