Composing Custom Hooks
In this page:
Combining Multiple Hooks into One
A custom hook isn't limited to calling only built-in hooks like useState — it can also call other custom hooks. This lets you build a more powerful hook out of smaller, focused pieces, the same way components are built out of smaller components.
Note: Keep each individual hook focused on one responsibility (like useToggle or useLocalStorage), then compose them where you need combined behavior.
Warning: A hook that composes five other hooks can become just as hard to follow as a giant component — split further if it grows unwieldy.
Example: Combining Multiple Hooks into One
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function useToggle(initial) {
const [value, setValue] = React.useState(initial);
return [value, () => setValue(v => !v)];
}
function useCounter(start) {
const [count, setCount] = React.useState(start);
return [count, () => setCount(c => c + 1)];
}
function useComposed() {
const [isOn, toggle] = useToggle(false);
const [count, increment] = useCounter(0);
return { isOn, toggle, count, increment };
}
function App() {
const c = useComposed();
return <button onClick={() => { c.toggle(); c.increment(); }}>{c.isOn ? "ON" : "OFF"} ({c.count})</button>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
A Real Example: useOnlineStatus + useLocalStorage
Composition shines when you combine hooks that each solve a different problem. Here, a useLastSeen hook composes an online-status hook with a localStorage hook, so it can record and persist the last time the user was online.
Note: Name the composed hook after the combined behavior it provides, not after the hooks it happens to use internally.
Warning: Watch for effects in composed hooks running in an order you don't expect — React runs effects in the order they're declared, top to bottom.
Example: A Real Example: useOnlineStatus + useLocalStorage
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function useLocalValue(key, initial) {
const [value, setValue] = React.useState(initial);
React.useEffect(() => { localStorage.setItem(key, value); }, [key, value]);
return [value, setValue];
}
function useLastSeen() {
const [lastSeen, setLastSeen] = useLocalValue("lastSeen", "never");
return [lastSeen, () => setLastSeen(new Date().toLocaleTimeString())];
}
function App() {
const [lastSeen, markSeen] = useLastSeen();
return <div><p>Last seen: {lastSeen}</p><button onClick={markSeen}>Mark as seen now</button></div>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
Keeping Composed Hooks Readable
As you compose more hooks together, naming and structure matter more. Destructuring each inner hook's result with clear names, and returning a well-named object from the outer hook, keeps the composed hook easy to understand at a glance.
Note: If a composed hook's return value has more than 4-5 fields, consider whether it's really doing too many unrelated things.
Warning: Avoid returning inconsistent shapes (sometimes an array, sometimes an object) across your custom hooks — pick one convention and stick to it.
Example: Keeping Composed Hooks Readable
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function useToggle(initial) {
const [value, setValue] = React.useState(initial);
return { value, toggle: () => setValue(v => !v) };
}
function useCounter(start) {
const [count, setCount] = React.useState(start);
return { count, increment: () => setCount(c => c + 1) };
}
function useFeature() {
return { visibility: useToggle(true), clicks: useCounter(0) };
}
function App() {
const { visibility, clicks } = useFeature();
return <div>{visibility.value && <p>Clicks: {clicks.count}</p>}<button onClick={clicks.increment}>Click</button><button onClick={visibility.toggle}>Toggle</button></div>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
- Building one giant hook that does everything instead of composing several small, focused hooks together.
- Not realizing that a hook can call other custom hooks — beginners sometimes think only built-in hooks can be combined.
- Creating circular dependencies between hooks, where two custom hooks each try to depend on the other's output.
- Hook composition means building a custom hook out of other hooks, including other custom hooks.
- It lets you build complex, reusable behavior (like useAuthenticatedFetch) from small, single-purpose hooks.
- Each composed hook still follows the same rules of hooks (top-level calls, no conditionals).
- This mirrors how small components compose into bigger components — hooks compose the same way for logic.
Available since React 16.8 (Hooks introduction) — composition is a JavaScript pattern, not a browser feature.
Chapter Quiz — Complete all 10 topics to unlock
0/10 topics done
Complete these topics first: