← Back to React Course | Chapter 6: Custom Hooks & Advanced Patterns | Lesson 6 of 10

Composing Custom Hooks

Hook composition is like stacking building blocks — you use several small custom hooks together inside one bigger custom hook to build something more powerful.

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

markup
<!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

markup
<!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

markup
<!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>
Common Mistakes
  1. Building one giant hook that does everything instead of composing several small, focused hooks together.
  2. Not realizing that a hook can call other custom hooks — beginners sometimes think only built-in hooks can be combined.
  3. Creating circular dependencies between hooks, where two custom hooks each try to depend on the other's output.
Chapter Summary
  • 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.
Browser Support

Available since React 16.8 (Hooks introduction) — composition is a JavaScript pattern, not a browser feature.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.