React state management is not a single tool — it’s a spectrum from local component state to global app state, and picking the wrong layer creates unnecessary complexity. useState for simple local state, useReducer for complex local state, Context for configuration, Zustand for app-wide state.
⚡ TL;DR: useState for simple primitive/object local state. useReducer when next state depends on previous in complex ways. Context for global config/theme/auth (not high-frequency updates). Zustand for global state with frequent updates — simpler than Redux, more capable than Context.
useState — simple local state — React state management
// Simple state: use useState
function Counter() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
// Independent pieces of state — separate useState calls
return ;
}
// Object state: OK for related values
function Form() {
const [form, setForm] = useState({name:'',email:'',age:''});
const update = field => e =>
setForm(prev=>({...prev,[field]:e.target.value}));
return (
<>
>
);
}useReducer — complex state transitions
// Use when: multiple sub-values, next state depends on previous
const initialState = { status:'idle', data:null, error:null };
function reducer(state, action) {
switch(action.type) {
case 'FETCH_START': return {...state, status:'loading', error:null};
case 'FETCH_SUCCESS': return {status:'success',data:action.payload,error:null};
case 'FETCH_ERROR': return {...state, status:'error', error:action.error};
case 'RESET': return initialState;
default: throw new Error('Unknown action: '+action.type);
}
}
function DataFetcher({url}) {
const [state, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
dispatch({type:'FETCH_START'});
fetch(url).then(r=>r.json())
.then(data=>dispatch({type:'FETCH_SUCCESS',payload:data}))
.catch(err=>dispatch({type:'FETCH_ERROR',error:err.message}));
}, [url]);
}Context — global config, not frequent state
// GOOD: Context for slow-changing global values
const ThemeContext = createContext('light');
const AuthContext = createContext(null);
// BAD: Context for frequent updates (every update re-renders all consumers)
// Don't use Context for: shopping cart (updated on every add/remove)
// Do use Context for: theme, locale, auth user, feature flags
function App() {
const [theme, setTheme] = useState('light');
return (
);
}
// Consume:
const {theme} = useContext(ThemeContext);Zustand — simple global state
import {create} from 'zustand';
// Define store
const useCartStore = create((set,get) => ({
items: [],
total: 0,
addItem: (item) => set(state => ({
items: [...state.items, item],
total: state.total + item.price
})),
removeItem: (id) => set(state => ({
items: state.items.filter(i=>i.id!==id),
total: state.total - (state.items.find(i=>i.id===id)?.price||0)
})),
clear: () => set({items:[],total:0})
}));
// Use in any component (no Provider needed!)
function CartIcon() {
const {items} = useCartStore();
return {items.length};
}- ✅ useState: primitive values, independent state pieces
- ✅ useReducer: multiple related values, complex transitions
- ✅ Context: slow-changing global (theme, auth, locale)
- ✅ Zustand: global state with frequent updates — simpler than Redux
- ❌ Don’t use Context for high-frequency updates — all consumers re-render
- ❌ Don’t install Redux for small apps — overkill, Zustand is simpler
External reference: Zustand documentation.
Recommended Reading
→ Designing Data-Intensive Applications — The bible of distributed systems and production engineering at scale.
→ The Pragmatic Programmer — Timeless engineering wisdom every senior developer needs.
Affiliate links. We earn a small commission at no extra cost to you.
Free Weekly Newsletter
🚀 Join 2,000+ Senior Developers
Get expert-level JavaScript, Python, AWS, system design and AI secrets every week. Zero fluff, pure signal.
Related: React Hooks Explained: useState, useEffect, useCallback, useMemo, useRef In Depth · Day 94: Change Management for Prompts · Day 64: Secret-Aware Context Filters
Related: React Performance Optimization: Every Technique From memo to Virtualization · Bedrock Prompt Management: Versioned System Prompts for Production · Amazon Q Developer: Custom Context Packs for Private Monorepos
Watch: 100 Days of AI on YouTube
Short videos from CheatCoders, one AI engineering topic per day.
Most viewed
- Python Decorators Explained: From Simple Wrappers to Production Patterns
- Claude Code vs Cursor vs GitHub Copilot: Choose by Workflow, and Set Up One Repo That Works With All Three
- AI Agent Frameworks in 2025: LangGraph vs CrewAI vs AutoGen vs Raw API
- Python String Methods: Every str Method With Real Production Examples
- API Debugging in 30 Seconds: The Fastest Ways to Fix API Issues
Newly added
- n8n vs AI Agent: I Gave Both the Same Job (Local, Ollama)
- Local AI Video Generator: Build Your Own App (Free, Python, No GPU)
- AI Wrote Raw Binary Instead of Code. Here’s What Happened (WebAssembly)
- I Built a Browser That Only Runs Encrypted Binary (Ep 1)
- RAG From Scratch: Chunking, Embeddings, Reranking and Evaluation (Real Numbers)
Deep-dive PDF
Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.
Already subscribed? or open the subscribe page.
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.
