Hooks Overview
What hooks are, the rules that govern them, and which ones you will actually reach for.
What a hook is
A hook is a function that lets a component use React features that live outside the render itself — state, side effects, context, refs. Every hook is named with the prefix use, which is how React and its lint rules recognise them.
Hooks arrived in React 16.8. Before that, only class components could hold state, and reusing stateful logic between components required awkward patterns. Hooks made function components complete.
The rules of hooks
Two rules, and both are enforced by the ESLint plugin that ships with most React setups.
Call hooks at the top level
Never call a hook inside a condition, a loop, or a nested function. React matches hooks to their stored values by call order, so the order must be identical on every render.
// Wrong — the hook is skipped on some renders
function Profile({ user }) {
if (user) {
const [name, setName] = useState(user.name);
}
}
// Right — hook first, condition after
function Profile({ user }) {
const [name, setName] = useState(user?.name ?? "");
if (!user) return null;
}Call hooks only from React functions
Hooks may be called from a component, or from another hook you have written yourself. Not from a plain utility function, an event handler, or a class.
The built-in hooks worth knowing
- useState — local state. Chapter 6.
- useEffect — synchronise with something outside React. Chapter 12.
- useContext — read shared data without passing props down. Chapter 13.
- useRef — a value that persists without causing re-renders, and DOM access. Chapter 14.
- useReducer — state with several related transitions, as an alternative to many useState calls.
- useMemo and useCallback — cache a computed value or a function between renders.
- useId — generate a stable unique id for linking labels to inputs.
useReducer in brief
When several state values change together according to a fixed set of actions, a reducer keeps the logic in one place and out of the component.
function reducer(state, action) {
switch (action.type) {
case "increment": return { count: state.count + 1 };
case "decrement": return { count: state.count - 1 };
case "reset": return { count: 0 };
default: throw new Error("Unknown action: " + action.type);
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<>
<span>{state.count}</span>
<button onClick={() => dispatch({ type: "increment" })}>Add</button>
<button onClick={() => dispatch({ type: "reset" })}>Reset</button>
</>
);
}useMemo and useCallback
Both cache something between renders and both recompute when their dependency array changes. useMemo caches a value; useCallback caches a function.
const sorted = useMemo(
() => [...items].sort((a, b) => a.price - b.price),
[items]
);
const handleSelect = useCallback(
id => setSelected(id),
[]
);These are optimisations, not defaults. Adding them everywhere costs memory and readability while measurably helping almost nothing. Reach for them when a computation is genuinely expensive or when a memoised child re-renders too often.
Custom hooks
Any function that starts with use and calls other hooks is a custom hook. This is how stateful logic gets shared between components — not by sharing state, but by sharing the recipe for it. Chapter 14 covers writing them.