Composition and Custom Hooks
Two ways to reuse logic in React: composing components together, and extracting stateful logic into a custom hook. Knowing which tool fits which problem.
What you'll learn
- Distinguish component composition from custom hooks as two different reuse mechanisms
- Extract a piece of stateful logic into a reusable custom hook shape
- Explain the naming convention and rule that makes a function a valid hook
Prerequisites
Explanation
React gives you two genuinely different tools for reuse, and reaching for the wrong one produces awkward code even when it technically works. Component composition reuses markup structure -- a Card component that renders a consistent border/padding/shadow around whatever children it's given is reused by wrapping different content in it each time. Custom hooks reuse stateful behavior -- the actual logic of managing some piece of state and the operations on it, with no markup involved at all.
A custom hook is not a special React construct with new syntax -- it's an ordinary JavaScript function that happens to call other hooks (useState, useEffect, or other custom hooks) internally, and by convention starts with "use" so React's tooling (and the Rules of Hooks linter) can recognize it and enforce the same call-order rules from the state lesson. A component that repeats the exact same useState-plus-toggle-function pattern in three different places has found a real custom hook waiting to be extracted:
function useToggle(initialValue = false) {
const [value, setValue] = useState(initialValue);
const toggle = () => setValue((v) => !v);
return [value, toggle];
}
Now const [isOpen, toggleOpen] = useToggle(false); replaces three lines of repeated boilerplate with one, in every component that needs the same on/off behavior -- a mobile nav drawer, an accordion section, a modal's visibility, all sharing the identical underlying logic without sharing any markup at all.
The naming convention isn't cosmetic. React's hook rules (call hooks only at the top level, only from React functions, always in the same order) apply transitively to anything that calls a hook internally -- a function named "use..." signals to both humans and linting tools that those rules apply to it too, the same way they'd apply to useState directly.
Example
The useToggle custom hook pattern, modeled with a plain closure so its shape and behavior are inspectable without a React runtime.
function useToggle(initialValue) {
let value = initialValue;
const listeners = [];
function get() { return value; }
function toggle() {
value = !value;
listeners.forEach((fn) => fn(value));
}
function subscribe(fn) { listeners.push(fn); }
return { get, toggle, subscribe };
}
const nav = useToggle(false);
nav.subscribe((v) => console.log("nav is now:", v));
nav.toggle(); // "nav is now: true"
nav.toggle(); // "nav is now: false"Try it yourself
Create a second, independent toggle for a modal and confirm toggling one doesn't affect the other.
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Guided exercise
Guided exercise
Write useCounter(initialValue) modeled as a closure returning { get, increment, decrement, reset } -- reset should restore the count to its ORIGINAL initialValue, not to 0.
Checks: increment works correctly · decrement works correctly · reset restores the original initial value
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Stuck? Get a hint.
Independent exercise
Independent exercise
Write isValidHookName(name) that returns true only if the string starts with 'use' followed immediately by an uppercase letter (the real convention: useState, useToggle, useCounter -- not 'user', 'usage', or 'use_counter').
Checks: recognizes a valid hook name · recognizes useState as valid · rejects a word that merely starts with 'use' · rejects an incorrectly-cased continuation
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Stuck? Get a hint.
Common mistakes
- Reaching for a custom hook when the actual need is markup reuse (a wrapper component), or vice versa -- reusing the wrong kind of thing produces awkward code.
- Copy-pasting the same useState-plus-handler pattern into multiple components instead of recognizing it as a custom hook waiting to be extracted.
- Naming a helper function with hook-like internals something that doesn't start with 'use', hiding from tooling (and other developers) that the Rules of Hooks apply to it.
Knowledge check
Takeaway
Composition reuses markup structure; custom hooks reuse stateful logic — a custom hook is just a plain function calling other hooks, with the 'use' prefix signaling that the same call-order rules apply.
Summary
This lesson distinguished component composition from custom hooks as two different reuse mechanisms and built a closure-based useCounter to make a custom hook's shape and behavior concrete.
References
Your notes
Notes save automatically.
Finished this lesson?
Mark it complete to track your progress and schedule a future review.
AI tutor
The optional AI tutor isn't enabled in this deployment. All lessons, exercises, quizzes, and search work fully without it.