React Hooks can be confusing when you're new to them. This guide explains the most important ones with clear examples.
useState — Local component state
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Clicked {count} times
</button>
);
}
When to use: Anything the component needs to remember between renders — form values, toggles, counters.
Gotcha: State updates are asynchronous.
// ❌ This won't work as expected
setCount(count + 1);
setCount(count + 1); // Both use the same `count` value
// ✅ Use the updater function instead
setCount(prev => prev + 1);
setCount(prev => prev + 1); // Now it's +2
useEffect — Side effects
import { useEffect, useState } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
// Runs after every render where userId changes
fetch(`/api/users/${userId}`)
.then(r => r.json())
.then(setUser);
}, [userId]); // dependency array
return <div>{user?.name}</div>;
}
The dependency array:
-
[]— Run once on mount -
[value]— Run when value changes - No array — Run after every render (usually wrong!)
Always clean up subscriptions:
useEffect(() => {
const subscription = subscribe(userId);
return () => {
subscription.unsubscribe(); // cleanup!
};
}, [userId]);
useContext — Share state without prop drilling
// 1. Create the context
const ThemeContext = createContext('light');
// 2. Provide it at the top
function App() {
const [theme, setTheme] = useState('dark');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Page />
</ThemeContext.Provider>
);
}
// 3. Use it anywhere in the tree
function Button() {
const { theme } = useContext(ThemeContext);
return <button className={theme}>Click me</button>;
}
useRef — DOM references and mutable values
function TextInput() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return (
<>
<input ref={inputRef} />
<button onClick={focusInput}>Focus</button>
</>
);
}
Also useful for: storing values that shouldn't trigger re-renders.
const renderCount = useRef(0);
renderCount.current += 1; // Doesn't cause re-render
useMemo — Expensive calculations
function ProductList({ products, minPrice }) {
// This only recalculates when products or minPrice changes
const filtered = useMemo(() => {
return products.filter(p => p.price >= minPrice);
}, [products, minPrice]);
return <ul>{filtered.map(p => <li key={p.id}>{p.name}</li>)}</ul>;
}
Don't overuse this. Only use it when:
- The calculation is genuinely expensive
- You've measured it's causing performance issues
useCallback — Stable function references
function Parent({ id }) {
// Without useCallback, a new function is created every render
const handleClick = useCallback(() => {
doSomethingWith(id);
}, [id]);
return <ExpensiveChild onClick={handleClick} />;
}
When to use: When passing callbacks to React.memo wrapped children.
useReducer — Complex state logic
const initialState = { count: 0, error: null, loading: false };
function reducer(state, action) {
switch (action.type) {
case 'increment': return { ...state, count: state.count + 1 };
case 'decrement': return { ...state, count: state.count - 1 };
case 'reset': return initialState;
default: return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
}
Use instead of useState when: multiple pieces of state are related and change together.
Custom Hooks — Reuse logic
The real power of hooks is composability. Extract logic into custom hooks:
// Custom hook for API calls
function useApi(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch(url)
.then(r => {
if (!r.ok) throw new Error(`HTTP ${r.status}`);
return r.json();
})
.then(data => { setData(data); setLoading(false); })
.catch(err => { setError(err.message); setLoading(false); });
}, [url]);
return { data, loading, error };
}
// Use it
function UserList() {
const { data, loading, error } = useApi('/api/users');
if (loading) return <Spinner />;
if (error) return <Error message={error} />;
return <ul>{data.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
Quick reference
| Hook | Purpose |
|---|---|
useState |
Local state |
useEffect |
Side effects, data fetching |
useContext |
Share state without props |
useRef |
DOM refs, mutable values |
useMemo |
Memoize expensive calcs |
useCallback |
Stable callback references |
useReducer |
Complex state logic |
Building something with React? DevToolkit has free tools like a JSON formatter, UUID generator, and more — no signup needed.
Top comments (0)