Framework Guide
React State Management
State is what makes React apps interactive. Here's how useState, useEffect, and useContext work — with real examples and when to use each.
Quick answer: Use useState for local state, useEffect for side effects (fetching, subscriptions), useContext for data shared across many components, and a library like Zustand when Context gets unwieldy.
🧠 What Is State?
State is data that changes over time. When it changes, React re-renders the component with the new values — the UI always reflects the current state.
Examples of state:
- Form input values
- Whether a modal is open
- Fetched data from an API
- Current user info
- Cart items
useState — the Basic Hook
The most-used hook. Creates a piece of state and a setter function:
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}
When setCount is called, React re-renders the component and updates the DOM.
Functional Updates
When new state depends on old state, use the function form:
// Risky — might use stale state
setCount(count + 1);
// Safe — always uses the latest state
setCount(prev => prev + 1);
This matters when multiple updates fire in quick succession (clicks, effects).
💡 Rule of thumb: if the new value depends on the old value, always use the function form.
useEffect — Side Effects
Runs after render. Use it for fetching data, timers, or subscriptions:
import { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(setUser);
}, [userId]); // re-run when userId changes
if (!user) return <p>Loading...</p>;
return <p>{user.name}</p>;
}
The array at the end is the dependency list — the effect runs when any value in it changes. Empty array = run once on mount.
useContext — Shared State
When many components need the same data, avoid prop drilling with Context:
import { createContext, useContext, useState } from 'react';
const ThemeContext = createContext();
function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Current: {theme}
</button>
);
}
Any component inside the Provider can read or update the theme — no prop drilling.
📊 When to Use Which
- useState — simple local values: numbers, strings, booleans, small objects
- useReducer — complex state with multiple fields, or state updates with patterns
- useEffect — syncing with the outside world: fetch, timers, subscriptions
- useContext — theme, auth, language, cart — data shared across many components
- Zustand / Redux — global state that changes often and has complex update logic
🚫 Common Beginner Mistakes
- Mutating state directly. Never do
state.push(x)— always use the setter. - Forgetting dependencies in useEffect. Missing a dependency causes stale values.
- Overusing Context. Context is for rarely-changing data. Fast-changing values cause performance issues.
- Reaching for Redux too early. Start with useState + Context. Add a library only when you need it.
- Calling hooks conditionally. Hooks must run in the same order every render.
🎯 Practice Challenges
- Build a to-do list with useState for the items array
- Add a useEffect that saves the todos to localStorage on every change
- Create a ThemeContext that toggles light/dark mode across the app
- Fetch a list of users with useEffect and display them
- Refactor the to-do list to useReducer with add/toggle/delete actions
❓ Frequently Asked Questions
What is state in React?
Data that changes over time in a component. When it updates, React re-renders with the new values.
When should I use useState vs useReducer?
useState for simple values. useReducer when state is complex or updates follow a pattern.
State vs props?
Props come from the parent and can't be changed. State is owned by the component and can be updated.
When do I need global state?
When multiple distant components need the same data — auth, theme, cart. Use Context or Zustand.
Is Redux still used?
Yes, but less. Modern apps use useState, Context, or Zustand. Redux Toolkit remains common in enterprise.