Hooks
Hooks are functions that let function components tap into React’s features—state, lifecycle, context, and more. Introduced in React 16.8, they replaced class components entirely. This page is a working reference for the core Hooks you’ll reach for daily.
The Rules of Hooks
Two rules make Hooks reliable, and they’re enforced by the eslint-plugin-react-hooks linter:
- Only call Hooks at the top level. Never inside loops, conditions, or nested functions. React tracks Hooks by call order, so that order must be identical on every render.
- Only call Hooks from React functions. Call them in components or in custom Hooks (functions named
use…)—never in regular helpers.
// ❌ conditional Hook — breaks call order
if (loggedIn) { const [name, setName] = useState(""); }
// ✅ unconditional, top level
const [name, setName] = useState("");
useState — local state
Stores a reactive value; updating it re-renders the component. (Covered in depth in State & Events.)
const [open, setOpen] = useState(false);
Use it for any UI value that changes over time: toggles, inputs, counters.
useEffect — synchronize with the outside world
Runs side effects after render—subscriptions, timers, fetching, manual DOM work. The dependency array controls when it re-runs; the returned function cleans up.
useEffect(() => {
const id = setInterval(tick, 1000);
return () => clearInterval(id); // cleanup
}, []); // [] = run once on mount
Use it to connect to things React doesn’t manage. (Full treatment in the next chapter.)
useRef — a mutable box that survives renders
Holds a mutable .current value that persists across renders without triggering one. Two main uses: referencing a DOM node, and storing instance-like values.
function TextInput() {
const inputRef = useRef(null);
return (
<>
<input ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus</button>
</>
);
}
Use it for DOM access, timer IDs, or any value you want to remember but not render from.
useMemo — cache an expensive computation
Memoizes a value, recomputing only when its dependencies change.
const sorted = useMemo(
() => items.slice().sort(compareFn),
[items]
);
Use it when a calculation is genuinely expensive, or to keep an object/array reference stable across renders.
useCallback — cache a function
Memoizes a function, returning the same reference until dependencies change. It’s useMemo specialized for functions.
const handleSelect = useCallback((id) => {
setSelected(id);
}, []);
Use it when passing callbacks to memoized children (React.memo) so they don’t re-render needlessly.
useContext — read shared data without prop drilling
Reads the nearest value of a Context, letting deeply nested components consume data without threading props through every level.
const ThemeContext = createContext("light");
function Toolbar() {
const theme = useContext(ThemeContext);
return <div className={theme}>…</div>;
}
Use it for app-wide concerns: theme, current user, locale.
useReducer — structured state transitions
An alternative to useState for complex state with multiple sub-values or interdependent transitions. You dispatch actions to a reducer that returns the next state.
function reducer(state, action) {
switch (action.type) {
case "increment": return { count: state.count + 1 };
case "reset": return { count: 0 };
default: throw new Error("unknown action");
}
}
const [state, dispatch] = useReducer(reducer, { count: 0 });
// dispatch({ type: "increment" })
Use it when update logic is complex or the next state depends on the previous in non-trivial ways.
Comparison table
| Hook | Returns | Re-renders on change? | When to use |
|---|---|---|---|
useState | [value, setter] | Yes | Simple reactive state |
useReducer | [state, dispatch] | Yes | Complex / multi-step state logic |
useEffect | undefined | No | Side effects, subscriptions, fetching |
useRef | { current } | No | DOM refs, mutable values across renders |
useMemo | memoized value | No (caches) | Expensive computations, stable references |
useCallback | memoized function | No (caches) | Stable callbacks for memoized children |
useContext | context value | Yes | Avoid prop drilling for shared data |
Best Practices
- Obey the Rules of Hooks and keep the lint plugin on—it catches dependency and ordering bugs.
- Reach for
useStatefirst; only adoptuseReducerwhen state logic genuinely grows complex. - Don’t reach for
useMemo/useCallbackreflexively—measure first. Premature memoization adds noise without benefit. - Extract repeated Hook logic into custom Hooks (
useLocalStorage,useDebounce) to keep components clean. - Keep effect dependency arrays honest—list everything the effect reads.