- React
- Advanced Component Design Patterns and Error Boundaries
- Pattern Evolution: HOCs, Render Props & Custom Hooks
Pattern Evolution: HOCs, Render Props & Custom Hooks
React code reuse evolved through three eras: Higher-Order Components (HOCs) suffered from wrapper hell and prop collisions; Render Props solved prop collision but created callback pyramid hell; Custom Hooks solved both by sharing stateful logic flatly without altering the component tree.
Key Learning Objectives
Trace the historical evolution of React code reuse from Mixins to HOCs, Render Props, and Hooks.
Analyze the structural problems of Higher-Order Components: prop collisions, wrapper hell, and indirection.
Analyze the structural problems of Render Props: JSX callback nesting and verbosity.
Demonstrate how custom hooks deliver logic composition with zero component tree overhead.
The Interview Problem & Context
Compare how a cross-cutting concern (e.g. mouse coordinate tracking) is implemented across HOCs, Render Props, and Custom Hooks, highlighting why Hooks became the dominant pattern.
1import React, { useState, useEffect } from 'react';23// GENERATION 1: Higher-Order Component (HOC)4// Problem: Wrapper hell in React DevTools, prop collisions, hidden prop contracts5function withMouse(Component: React.ComponentType<{ mouse: { x: number; y: number } }>) {6 return function WithMouseComponent(props: any) {7 const [mouse, setMouse] = useState({ x: 0, y: 0 });8 useEffect(() => {9 const handleMove = (e: MouseEvent) => setMouse({ x: e.clientX, y: e.clientY });10 window.addEventListener('mousemove', handleMove);11 return () => window.removeEventListener('mousemove', handleMove);12 }, []);13 return <Component {...props} mouse={mouse} />;14 };15}1617// GENERATION 2: Render Props18// Problem: "Pyramid of doom" callback indentation when composing 3+ concerns19function MouseTracker({20 children,21}: {22 children: (mouse: { x: number; y: number }) => React.ReactNode;23}) {24 const [mouse, setMouse] = useState({ x: 0, y: 0 });25 useEffect(() => {26 const handleMove = (e: MouseEvent) => setMouse({ x: e.clientX, y: e.clientY });27 window.addEventListener('mousemove', handleMove);28 return () => window.removeEventListener('mousemove', handleMove);29 }, []);30 return <>{children(mouse)}</>;31}3233// GENERATION 3: Custom Hook (Modern Standard)34// Solution: Flat, zero wrapper nodes, full TypeScript inference, pure composability!35export function useMouse() {36 const [mouse, setMouse] = useState({ x: 0, y: 0 });37 useEffect(() => {38 const handleMove = (e: MouseEvent) => setMouse({ x: e.clientX, y: e.clientY });39 window.addEventListener('mousemove', handleMove);40 return () => window.removeEventListener('mousemove', handleMove);41 }, []);42 return mouse;43}4445// Modern Clean Usage46export function Canvas() {47 const { x, y } = useMouse();48 return <div>Cursor at: {x}, {y}</div>;49}
Deep Architectural Explanation
The Evolution of React Composition
1. Higher-Order Components (HOCs)
- Concept: Function taking a component and returning a new component (
withFeature(Comp)). - Defects:
- Wrapper Hell: DevTools tree had 10 nested wrapper components for a single visual element.
- Prop Collision: If
withAuthandwithDataboth injectuserorloading, one silently overwrites the other! - TypeScript Pain: Hard to infer prop types injected by HOC wrappers.
2. Render Props
- Concept: Component takes a function returning JSX as a prop (
<Mouse>{(pos) => ...}</Mouse>). - Benefits: Explicit data flow; no prop collision.
- Defects:
- Callback Hell: Composing
<Mouse>,<WindowSize>, and<Theme>resulted in 3 layers of nested functions before writing UI.
- Callback Hell: Composing
3. Custom Hooks (React 16.8+)
- Concept: JavaScript functions starting with
usecalling built-in hooks. - Why Hooks Won:
- Zero JSX nesting.
- Flawless TypeScript return type inference.
- Composable: one hook can pass values directly to another hook (
const user = useUser(); const posts = usePosts(user.id);).
Common Interview Traps & Pitfalls
Continuing to use HOCs for logic reuse in modern React where custom hooks are vastly superior.
Believing render props are completely dead: they are still useful for customizing UI slots (e.g., Virtualized lists row rendering).
Breaking the Rules of Hooks when migrating from render props to custom hooks.
Senior Interviewer Follow-Up Probes
When is an HOC still appropriate in modern React (e.g., React.memo, error boundaries)?
How does the Slots pattern compare to render props for custom layout injection?
Can custom hooks return JSX, or should they strictly return state and actions?
Frequently Asked Interview Questions
Why did Render Props replace HOCs in popularity before Hooks?
Render Props made prop sources explicit and completely solved prop collisions by passing data through function arguments rather than implicitly spreading props onto components.
Why are Custom Hooks superior to both HOCs and Render Props for logic reuse?
Custom Hooks allow stateful logic to be consumed directly in a flat component body without introducing any extra JSX wrappers, callback nesting, or component tree overhead.
Are there any cases where Render Props are still the best pattern?
Yes, when a library component controls the rendering lifecycle of items (such as virtualized list item renderers, animation transitions, or custom dropdown option renderers).
What is "Wrapper Hell" in legacy React codebases?
The deeply nested hierarchy of wrapper components in React DevTools produced by stacking multiple HOCs (e.g., connect, withRouter, withStyles, withAuth).
Can you convert any HOC into a custom hook?
Any HOC that exists purely to provide state or lifecycle logic can be converted to a custom hook. HOCs that conditionally render or wrap with DOM elements (like ErrorBoundary) may still require a component wrapper.
