Start with What React Already Gives You
useState, useReducer, and Context cover a large share of real applications. A dedicated state management library solves problems that appear once an app’s shared state becomes large, frequently updated, and needed across many unrelated parts of the tree.
The Problem: Context Re-Renders Everything
As covered earlier, every consumer of a Context re-renders whenever its value changes — fine for infrequently-changing data like theme or auth, but potentially costly for state that updates often and is read widely across the app.
Redux: Centralized, Predictable State
Redux centralizes all application state in a single store, updated only through dispatched actions and pure reducer functions — similar in spirit to useReducer, but app-wide, with excellent debugging tools (time-travel debugging, action logs) for complex state.
A Minimal Redux Toolkit Slice
import { createSlice, configureStore } from "@reduxjs/toolkit";
const counterSlice = createSlice({
name: "counter",
initialState: { value: 0 },
reducers: {
incremented: (state) => { state.value += 1; },
},
});
const store = configureStore({ reducer: counterSlice.reducer });Zustand: A Lightweight Alternative
Zustand offers a much smaller API surface than Redux — a store is just a hook, with no Provider wrapping required and far less boilerplate for common cases.
A Zustand Store
import { create } from "zustand";
const useCounterStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}));
function Counter() {
const { count, increment } = useCounterStore();
return <button onClick={increment}>{count}</button>;
}Comparing Options
| Tool | Boilerplate | Best For |
|---|---|---|
| useState / useReducer | Minimal | Local or lifted state within a subtree |
| Context | Low | Infrequently-changing, widely-shared data (theme, auth) |
| Zustand | Low | App-wide state with minimal setup |
| Redux Toolkit | Moderate | Large apps needing strict structure and powerful debugging tools |
Server State is a Different Problem
Data that originates from a server (API responses) has different needs — caching, refetching, staleness — than purely client-side UI state. Libraries like TanStack Query (covered in the data fetching lesson) specialize in exactly that, and are often used alongside a separate client-state tool for UI-only state.
Don’t Reach for a Library Prematurely
Many apps never actually need Redux or Zustand — Context plus a bit of careful component structuring is enough. Adding a state management library too early adds complexity and boilerplate the app doesn’t yet need.
Best Practice
Reach for a dedicated library once you notice prop drilling becoming unmanageable or Context re-renders becoming a measured performance problem — not by default at the start of every project.