React Curriculum

Pattern Evolution: HOCs, Render Props & Custom Hooks

Medium100% Free~25 mins#react#patterns#hoc#render-props#custom-hooks#evolution#refactoring
Interview Answer (30-Second Summary)

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';
2
3// GENERATION 1: Higher-Order Component (HOC)
4// Problem: Wrapper hell in React DevTools, prop collisions, hidden prop contracts
5function 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}
16
17// GENERATION 2: Render Props
18// Problem: "Pyramid of doom" callback indentation when composing 3+ concerns
19function 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}
32
33// 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}
44
45// Modern Clean Usage
46export 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 withAuth and withData both inject user or loading, 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.

3. Custom Hooks (React 16.8+)

  • Concept: JavaScript functions starting with use calling 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
What was the primary downside of Higher-Order Components (HOCs)?

Prop name collisions (when two HOCs inject identically named props), indirection in the component tree, and "wrapper hell" inflating the React DevTools component hierarchy.

Render Props made prop sources explicit and completely solved prop collisions by passing data through function arguments rather than implicitly spreading props onto components.

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.

Yes, when a library component controls the rendering lifecycle of items (such as virtualized list item renderers, animation transitions, or custom dropdown option renderers).

The deeply nested hierarchy of wrapper components in React DevTools produced by stacking multiple HOCs (e.g., connect, withRouter, withStyles, withAuth).

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.