Runnable lesson · press Try it on the examples to edit and run them here.
A custom hook is a function whose name starts with use and that calls other hooks. It lets you reuse stateful logic across components without repeating code or adding wrapper components.
When to write one
If two components contain the same combination of useState and useEffect logic, move it into a function. Components stay short and describe what they need; the hook knows how.
Example 1: useToggle
function useToggle(initial = false) {
const [value, setValue] = React.useState(initial);
const toggle = React.useCallback(() => setValue((v) => !v), []);
return [value, toggle];
}
function App() {
const [on, toggleOn] = useToggle();
return (
<div>
<p>{on ? "On" : "Off"}</p>
<button onClick={toggleOn}>Toggle</button>
</div>
);
}
The hook returns an array (like useState), so the caller can name the parts. Return an object instead when there are many values.
Example 2: useLocalStorage
This hook keeps state in localStorage so it survives a reload:
function useLocalStorage(key, initialValue) {
const [value, setValue] = React.useState(() => {
try {
const saved = localStorage.getItem(key);
return saved !== null ? JSON.parse(saved) : initialValue;
} catch {
return initialValue;
}
});
React.useEffect(() => {
try {
localStorage.setItem(key, JSON.stringify(value));
} catch {
// storage can be full or blocked: ignore
}
}, [key, value]);
return [value, setValue];
}
function App() {
const [name, setName] = useLocalStorage("demo-name", "");
return (
<div>
<label>Your name: <input aria-label="Name" value={name} onChange={(e) => setName(e.target.value)} /></label>
<p>Hello, {name || "stranger"}</p>
</div>
);
}
In your own project the value is really saved, and a reload brings it back. (The playground gives each run a temporary in-memory localStorage, because the sandbox cannot touch the real one.) Note the lazy initial state, and the try/catch, because storage can be unavailable.
Example 3: useDebounce
Wait until the user stops typing before using the value, which is perfect for search boxes:
function useDebounce(value, delay) {
const [debounced, setDebounced] = React.useState(value);
React.useEffect(() => {
const id = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(id); // cancel if the value changes again
}, [value, delay]);
return debounced;
}
function App() {
const [text, setText] = React.useState("");
const debounced = useDebounce(text, 400);
return (
<div>
<input aria-label="Search" placeholder="Search" value={text} onChange={(e) => setText(e.target.value)} />
<p>Typing: {text}</p>
<p>Searching for: {debounced}</p>
</div>
);
}
The rules
- The name must start with
use. That is how React and the linter know to apply the Rules of Hooks.useToggleis a hook;toggleis not. - Follow the Rules of Hooks inside it: top level only, same order.
- Each call gets its own state. Two components that call
useToggle()do not share anything. The hook shares logic, not data. To share data, use Context or lift state.
function useToggle() {
const [v, setV] = React.useState(false);
return [v, () => setV(!v)];
}
function App() {
const [a, toggleA] = useToggle();
const [b, toggleB] = useToggle();
return (
<div>
<p>A: {a ? "On" : "Off"}, B: {b ? "On" : "Off"}</p>
<button onClick={toggleA}>Toggle A</button> <button onClick={toggleB}>Toggle B</button>
</div>
);
}
Where to put them
In a project, give each hook its own file in a hooks/ folder (or next to the feature that uses it), named after the hook: useDebounce.js.
What makes a good custom hook?
- It has one clear purpose and a name that describes it.
- It hides details (the effect and cleanup) and exposes a small, easy interface.
- It is useful in more than one place. If used once, a plain function in the component is fine.
Do not create hooks named like lifecycle methods (useMount). Hooks that wrap useEffect should say what they do (useOnlineStatus), not when.
Key points
- A custom hook is a function starting with
usethat uses other hooks. - It shares logic between components, not data; each call has separate state.
- Follow the Rules of Hooks and give hooks clear, purposeful names.