Custom Hooks
Extracting reusable stateful logic out of components into your own functions, following the same rules as React's built-in hooks.
2 min read
A custom hook is just a JavaScript function whose name starts with use and that calls other hooks inside it. That's the entire mechanism — there's no special registration or configuration. It exists purely to let you extract logic that uses useState, useEffect, or other hooks out of a component and reuse it elsewhere.
Extracting a repeated pattern
Say several components each track whether a value is stored in localStorage, keeping it in sync on every change:
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const stored = localStorage.getItem(key);
return stored !== null ? JSON.parse(stored) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}Any component can now use it exactly like useState:
function SettingsPanel() {
const [darkMode, setDarkMode] = useLocalStorage("darkMode", false);
return (
<button onClick={() => setDarkMode(!darkMode)}>
{darkMode ? "Dark" : "Light"} mode
</button>
);
}SettingsPanel doesn't know or care that darkMode is backed by localStorage — it just sees a value and a setter, same as any useState. The persistence logic is fully contained inside the hook.
Each call gets its own independent state
Calling a custom hook in two different components doesn't share state between them — every call creates its own separate useState, useEffect, and so on, exactly as if you'd written that code directly inside each component:
function A() {
const [value] = useLocalStorage("count", 0); // independent state
}
function B() {
const [value] = useLocalStorage("count", 0); // also independent state
}A custom hook packages behavior, not shared state — if you actually need shared state between components, that's what Context (previous lesson) or lifting state up is for.
The rules still apply
Custom hooks are still hooks: they follow the same rules covered earlier — only call them at the top level of a component or another custom hook, never inside a condition or loop. The use prefix isn't just convention; it's what tells the eslint-plugin-react-hooks linter (and other developers) that a function is subject to those rules at all.
When to extract one
Reach for a custom hook when the same stateful logic — not just the same UI — shows up in more than one component: a debounced value, a media query listener, a fetch-and-cache pattern. If it's genuinely one-off, inlining it in the component is simpler and doesn't need the extra indirection.
The next lesson covers error boundaries — what happens, and what you can do about it, when a component throws during rendering.