AcademyReact
Render Props vs Higher-Order Components
The two pre-hooks patterns for sharing behavior between components, their well-known pitfalls, and why a hook replaces both.
3 min readUpdated 3 exercises
The problem both patterns solved
Before Hooks, a component's only reusable unit of behavior was the component itself. There was no way to extract "track the mouse position" or "read auth state" into something else a component could just call. Two patterns filled that gap, and both still show up constantly in code written before roughly 2019, and in interview questions today.
Higher-Order Component
A function that takes a component and returns a new one, injecting extra props:
function withMouse(Component) {
function WithMouse(props) {
const [pos, setPos] = useState({ x: 0, y: 0 });
useEffect(() => {
const handler = (e) => setPos({ x: e.clientX, y: e.clientY });
window.addEventListener('mousemove', handler);
return () => window.removeEventListener('mousemove', handler);
}, []);
return <Component {...props} mouse={pos} />;
}
WithMouse.displayName = `WithMouse(${Component.displayName || Component.name || 'Component'})`;
return WithMouse;
}
const CursorTracker = withMouse(({ mouse }) => <p>{mouse.x}, {mouse.y}</p>);WithMouse.displayName is not decoration — without it, React DevTools shows every HOC-wrapped component as the same generic name, and getDisplayName-style helpers exist specifically to build that string consistently across a codebase.
Render Props
A component that takes a function — as children or as a named prop — and calls it with the data, instead of injecting a prop onto a component you don't control:
function Mouse({ children }) {
const [pos, setPos] = useState({ x: 0, y: 0 });
useEffect(() => {
const handler = (e) => setPos({ x: e.clientX, y: e.clientY });
window.addEventListener('mousemove', handler);
return () => window.removeEventListener('mousemove', handler);
}, []);
return children(pos);
}
<Mouse>{(mouse) => <p>{mouse.x}, {mouse.y}</p>}</Mouse>Same behavior, same cleanup, no component wrapping involved — the caller decides what to render with the data, inline, at the call site.
Where HOCs bite
Two problems, both well documented and both reasons the community moved off this pattern:
Prop collisions. withMouse injects a prop named mouse. If the wrapped component already receives a prop called mouse from somewhere else — another HOC, a parent — one silently overwrites the other, and which one wins depends on wrapping order, not on anything visible at either call site.
Wrapper hell. Compose three or four HOCs — withMouse(withAuth(withTheme(Component))) — and the result is that many extra layers in the component tree. React DevTools shows a stack of wrapper components between the thing you're debugging and its actual parent, and tracing which HOC contributed which prop means reading every wrapper's source, in order.
Render props avoid the collision problem — the function's parameters have whatever names you give them at the call site — but they trade it for their own kind of nesting: several render-prop components composed together produce the same rightward-drifting "callback hell" shape that made promise chains painful before async/await.
Why hooks won
useMouse() — a custom hook wrapping the same useState + useEffect pair — adds zero components to the tree, so there is no wrapper to see in DevTools and no prop to collide with anything. Composing several is just calling several hooks in one component body:
function CursorTracker() {
const mouse = useMouse();
const { user } = useAuth();
return <p>{user.name} at {mouse.x}, {mouse.y}</p>;
}No wrapping, no injected prop name to collide with, no render-prop nesting. For new code this is strictly the better option — but HOCs and render props were the only tools available for years, they are still how a lot of production code and a lot of library APIs are written, and recognizing both on sight — especially the Component => Component shape of a HOC — is still a fair thing for an interviewer to expect.
Now practice it
2 challenges · 1 quizReading this page is the cheap half. These are the exercises that make you use it.
- Render Props PatternProImplements the render-prop half directly — a component that calls a function child with live data.ChallengeSolve · MediumChallengeSolve · Medium
- getDisplayName — HOC UtilityProBuilds the exact displayName utility a HOC needs so wrapped components stay readable in DevTools.ChallengeSolve · EasyChallengeSolve · Easy
- Performance · Profiling & OptimizationHarder questions on the tree-shape cost of wrapper components, which is where wrapper hell actually bites.QuizTake · HardQuizTake · Hard
Read next
Compound Components
How Context replaces a giant prop API with a group of components that implicitly share state through composition.
3 min
Writing Custom Hooks
A custom hook is a plain function that calls other hooks. It shares logic, not state — two components using it get two independent copies.
5 min