Choosing a State Management Solution
In this page:
Local State: useState and useReducer
If a piece of state is only used by one component (or that component and its direct children via props), plain useState or useReducer is almost always the right, simplest choice. Reaching for a global state library here adds complexity with no real benefit.
Note: Start every new piece of state as local state, and only lift it up or move it to shared state management once you actually need to share it.
Warning: Prematurely putting local-only state into Context or Redux 'just in case' adds indirection without a matching benefit.
Example: Local State: useState and useReducer
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function App() {
const [isOpen, setIsOpen] = React.useState(false);
return <button onClick={() => setIsOpen(!isOpen)}>{isOpen ? "Close" : "Open"} Menu</button>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
Shared State: Context, Zustand, or Redux
When state needs to be read or updated by components in different, unrelated branches of the tree, you need something beyond local state. Context API is built into React and fine for moderate cases; Zustand/Jotai offer a lighter-weight external option; Redux offers the most structure and tooling, valuable at larger scale.
Note: A rough decision path: try Context first; if update frequency or app size starts to strain it, consider Zustand or Redux Toolkit next.
Warning: There's no single best choice — the right tool depends on team size, app complexity, and how often the shared state actually changes.
Example: Shared State: Context, Zustand, or Redux
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const ThemeContext = React.createContext("light");
function ThemeLabel() {
const theme = React.useContext(ThemeContext);
return <p>Shared via Context (built-in, no install): {theme}</p>;
}
function App() {
return <ThemeContext.Provider value="dark"><ThemeLabel /></ThemeContext.Provider>;
}
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
</script>
</body>
</html>
A Practical Decision Checklist
In practice: does only one component (or its direct children) need this? Use useState. Do a handful of components across the tree need it, without constant rapid changes? Context is fine. Does a large app need it with complex update logic, strong DevTools, and team-wide conventions? Consider Redux Toolkit. Want something in between with minimal setup? Try Zustand.
Note: It's completely normal for one app to use several of these together — local useState for widgets, Context for theme, Redux for core business data.
Warning: Don't treat this as a one-time decision for the whole app — different pieces of state can reasonably use different tools.
Example: A Practical Decision Checklist
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
// A real app often mixes approaches:
// - useState: form input, modal open/closed
// - Context: theme, current logged-in user
// - Redux/Zustand: shopping cart, notifications, complex shared business data
function Example() {
const [open, setOpen] = React.useState(false); // local
return <p>{open ? "Open" : "Closed"}</p>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<Example />);
</script>
</body>
</html>
- Reaching for Redux by default on every project, regardless of actual complexity.
- Using Context for extremely frequently-changing state (like mouse position), which can cause performance issues since every consumer re-renders on each change.
- Never revisiting the choice as an app grows — a tool that was fine at the start can become a bottleneck later.
- useState/useReducer are enough for state local to one component or a small group.
- Context API suits state shared across a moderate number of components without frequent, rapid updates.
- Redux (or Redux Toolkit) suits large apps with complex, frequently-changing, widely-shared state and a need for strong tooling/conventions.
- Zustand/Jotai/Recoil offer lighter-weight alternatives to Redux for shared state, trading some structure for simplicity.
No React-version restriction for the comparison itself — see each tool's own npm package requirements.
Chapter Quiz — Complete all 10 topics to unlock
0/10 topics done
Complete these topics first: