Custom hooks
What a custom hook is
A custom hook is a regular JS function whose name starts with use, and which uses other React hooks (useState, useEffect, etc.) inside it.
It exists to extract and reuse logic (state, effects, subscriptions), without duplicating it across components.
The idea: "share behavior, not markup". The component stays clean, the UI stays separate.
How to build one (micro-examples)
1) Basic: useToggle
javascript
import { useState, useCallback } from "react";
export function useToggle(initial = false) {
const [on, setOn] = useState(initial);
const toggle = useCallback(() => setOn(v => !v), []);
const setTrue = useCallback(() => setOn(true), []);
const setFalse = useCallback(() => setOn(false), []);
return { on, toggle, setTrue, setFalse };
}2) With a side effect and cleanup: useDebounce
javascript
import { useEffect, useState } from "react";
export function useDebounce(value, delay = 300) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const id = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(id); // cleanup
}, [value, delay]);
return debounced;
}3) With local storage: useLocalStorage
javascript
import { useEffect, useState } from "react";
export function useLocalStorage(key, initial) {
const [state, setState] = useState(() => {
const raw = localStorage.getItem(key);
return raw != null ? JSON.parse(raw) : initial;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(state));
}, [key, state]);
return [state, setState]; // a familiar interface, like useState
}The rules (restrictions) of hooks - must know
- A name starting with
useThe function must be calleduseSomething. This is not "magic", it is a convention that the linter checks. - Only call hooks at the top level Not inside conditions, loops, or nested functions. The order of calls must be the same on every render.
javascript
// bad
if (cond) { useEffect(...); }
// good
useEffect(() => { if (cond) {/*...*/} }, [cond]);- Only call hooks from React functions From function components or other hooks. Not from regular functions, not from event handlers, not from classes.
- Custom hooks are synchronous functions
The hook itself must not be
async. Asynchrony belongs inside the effects. - Follow
exhaustive-depsThe dependencies of effects/memoizations must be correct. Useeslint-plugin-react-hooksto enforce this. - Do not mix responsibilities A hook is about one task (fetching, toggling, forms, syncing with storage, and so on). Small, composable hooks are better than one "giga-hook".
- Stable references outward
If the hook returns functions/objects, memoize them (
useCallback/useMemo) so consumers do not re-render unnecessarily. - It does not render UI The hook returns data/methods, not JSX. Drawing is the component's job.
- SSR specifics
Anything that depends on
window/document/localStorageshould be checked inside effects or conditionally (these APIs do not exist on the server).
When to build a custom hook
- You are repeating the same
useEffect + useStatein several components. - You need to encapsulate complex logic (forms, requests, web sockets, URL↔state sync).
- You want to provide a clean, testable API for the behavior.
Recommendations for hook APIs
- Return a primitive, predictable contract (an array like
useState, or an object with named fields). - Document the dependencies (what you pass into the hook, what it returns).
- Minimize outside effects: keep anything with side effects inside
useEffectwith carefulcleanup.
Short Answer
Interview readyPremium
A concise answer to help you respond confidently on this topic during an interview.