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
Most viewed
- Python Decorators Explained: From Simple Wrappers to Production Patterns
- Agentic Git Workflows: Atomic Commits From Noisy LLM Diffs
- Python String Methods: Every str Method With Real Production Examples
- REST API Design Best Practices: The Patterns That Make APIs a Joy to Use
- AI Agent Frameworks in 2025: LangGraph vs CrewAI vs AutoGen vs Raw API
Newly added
- Amazon Bedrock Model Evaluation: Score Coding-Agent Outputs Before You Promote a Prompt
- AWS App Runner: Host Coding-Agent HTTP APIs Without Babysitting Containers
- Amazon S3 Object Lock: Immutable Artifact Buckets Coding Agents Cannot Overwrite
- AWS Security Hub: Aggregate Coding-Agent Security Findings Across Accounts
- Amazon Detective: Investigate Compromised Coding-Agent Credentials After GuardDuty
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.
