Custom hooks
Extract reusable stateful logic into your own use… functions: useLocalStorage, useFetch, useDebouncedValue and more.
As you build components, you'll notice the same hook logic appearing again and again: subscribing to the window size, syncing state with localStorage, fetching data, debouncing input. Custom hooks let you extract that logic into a function with a good name and reuse it anywhere. They're one of React's most powerful ideas — and they're just functions.
Your first custom hook#
Here's an online-status effect from an earlier lesson:
Move it into a function whose name starts with use:
Components now say what they need (useOnlineStatus()), not how it works.
The rules#
- Name it
useSomething. The prefix tells React's linter to check the Rules of Hooks inside it, and tells readers it may contain state or effects. - Follow the Rules of Hooks: call hooks only at the top level of the custom hook, never conditionally.
- Each call is independent. Custom hooks share logic, not state. Two components calling
useOnlineStatus()each get their own state variable (they just happen to hold the same value). - If a function doesn't call any hooks, it doesn't need to be a hook — make it a regular function (
formatPrice, notuseFormatPrice).
useToggle#
Returning an array (like useState) lets callers name the values; returning an object ({ on, toggle }) is clearer when there are many values.
useLocalStorage: state that survives a refresh#
It has exactly the same API as useState, so swapping one for the other is a one-word change.
useDebouncedValue#
Wait until the user stops typing before searching:
Hooks compose: Search combines useState, useDebouncedValue and useFetch (from the Fetching data lesson). The input stays responsive while requests only fire 400 ms after typing stops.
useFetch, improved#
Keeping the previous data while loading avoids the list flickering empty on each keystroke.
useMediaQuery#
Hooks that accept callbacks#
When a hook takes a callback (an event handler), you usually want the latest version without re-subscribing every render. Store it in a ref:
Since React 19.2 there's an official hook for this pattern, useEffectEvent. It wraps a function so that it always sees the latest props and state, but doesn't count as an effect dependency:
Only call effect events from inside effects. Don't pass them to other components or call them during rendering.
Designing good custom hooks#
- Name for the purpose, not the mechanism:
useOnlineStatus,useCart,useCourseProgress— notuseEffectWithListener. - Keep inputs and outputs simple: take plain values, return values and functions.
- Don't wrap effects for the sake of it. A custom hook should make the component simpler to read. Avoid "lifecycle" hooks like
useMount(fn)— they hide dependencies. - Test them with React Testing Library's
renderHook(see Testing). - Check for existing ones: libraries like
usehooks-tsand@uidotdev/usehooksprovide well-tested versions of common hooks.
Common mistakes#
- Expecting two components using the same hook to share state.
- Calling a custom hook conditionally or inside a loop.
- Naming a non-hook helper
useSomething(or a hook withoutuse, which disables lint checks). - Returning new objects or functions every render when callers use them as effect dependencies — memoise with
useMemo/useCallbackwhere it matters.
What's next#
Next: lifting state up and composition patterns — how to structure components so data flows cleanly.
Check your understanding
Quick quiz
1.What must a custom hook's name start with?
2.Two components both call
useToggle(). Do they share the same state?3.Which is a good reason to write a custom hook?
Finished reading?
Mark this lesson complete to track your progress.