What is it?
A custom hook is just a function that calls other hooks. It bundles
related state + effects + handlers behind one name so a component can
reuse the behavior. useDebounce, useLocalStorage, useMediaQuery,
useFetch — all the same pattern: extract logic, return values.
Why it matters
Components without custom hooks become tangled balls of useState +
useEffect calls. The same data-fetch logic copied into 12 components
is the bug-fix tax that hits month four. A clean custom hook turns
that into one import.
What to learn
- The naming convention: must start with
use - What to return: tuple, object, or single value (and when each fits)
- Stable references: when to wrap returned functions in
useCallback - Testing custom hooks with
@testing-library/reactrenderHook - The "controlled" pattern: hooks that accept config and return state
- When a custom hook is the wrong abstraction — sometimes a regular function is enough
Common pitfall
Over-extracting. A hook used in exactly one component is rarely worth the indirection. Build the logic in the component first; extract when you need it twice. Premature hooks are harder to read than the inline code they replaced.
Resources
Primary (free):
- React docs — Reusing Logic with Custom Hooks · docs
- usehooks.com · article
- TkDodo — Why custom hooks · article
Practice
Write useLocalStorage(key, initialValue). It should read from
localStorage on mount, return a [value, setValue] tuple, and write
back on every change. Use it in two different components. Done when both
components share state across page reloads with no extra code.
Outcomes
- Extract a reusable hook from inline component logic.
- Pick the right return shape (tuple, object, single value) for any hook.
- Write a hook that handles both client + SSR safely.
- Recognize when a regular function would beat a custom hook.