codejump

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

Waiting at the end of this page.

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:

JSX
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:

JSX
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:

JSX
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 quiz

Reading this page is the cheap half. These are the exercises that make you use it.