Lesson 65 of 80
A component that calls four custom hooks and hands their results to five children has traded prop drilling in for prop drilling right back out — here's how to not do that.
13 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
A custom hook is just a function — nothing stops a component from calling several of them: useAuth(), useCart(), useTheme(). The problem shows up one layer down: if the components that actually need user, cartCount, and theme are three levels deeper in the tree, someone has to carry those values down through every component in between. Calling the hooks at the top didn't remove prop drilling — it just moved the drilled values from "props the parent already had" to "return values three hooks just handed it."
There are three real fixes, and which one's right depends on where the value is actually needed.
1. Compose into one hook, if the children genuinely need most of it together:
function useAppShell() {
const auth = useAuth();
const cart = useCart();
const theme = useTheme();
return { user: auth.user, cartCount: cart.items.length, theme };
}
function Header() {
const { user, cartCount, theme } = useAppShell(); // one prop's worth of drilling, not three
// ...
}
This helps when several return values travel together — it turns three drilled props into one, but it's still drilling if Header's children need pieces of it too.
2. Pair the composed hook with Context, when the value is needed broadly and changes rarely:
const AppShellContext = createContext(null);
function AppShellProvider({ children }) {
const shell = useAppShell(); // called once, at the top
return <AppShellContext.Provider value={shell}>{children}</AppShellContext.Provider>;
}
function CartBadge() {
const { cartCount } = useContext(AppShellContext); // read directly, no drilling at all
return <span>{cartCount}</span>;
}
No props travel through Header, Nav, or anything else in between — CartBadge reads what it needs directly, wherever it's mounted.
3. Call the hook again, lower in the tree, if it's safe to call twice:
function ProfileMenu() {
const { user } = useAuth(); // called again here instead of threaded from the root
return <span>{user.name}</span>;
}
If useAuth reads from a stable source (a context, a subscription, a cache) rather than owning its own independent piece of useState, calling it again wherever it's needed is often simpler than either composing or drilling at all — there's no prop to pass because there's no dependency on an ancestor's call.
useAppShell() still needs to travel as one prop if consumers are several layers down; Context is what removes the travel entirely, not the act of combining hooks.cartCount, colocating the call there (option 3, if safe) is simpler than standing up a Provider for one value.useAppShell calling useAuth and useCart still runs two fully independent hooks in sequence — there's no shared render cycle or batching benefit, just one function bundling two return values into one object.This is the prop-drilling problem from earlier in this track, showing up specifically at the boundary where hook return values become the thing traveling down instead of a plain prop the parent already owned. The same fix — Context — applies for the same reason: a value provided once, read anywhere below, without every component in between needing to know it exists. What's new here is only recognizing that a hook's return value is drilling material exactly like any other prop, not something exempt from the problem because it came from a hook instead of a parent.