C.W.K.
Stream
Lesson 06 of 07 · published

Patterns That Quietly Died — HOCs, Render Props, Classes

~12 min · history, anti-patterns, hooks

Level 0React Novice
0 XP0/54 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
You will see these patterns in old codebases. You should not write them in new code. Knowing why each one died is more useful than memorizing the death dates.

Class components

The original React component model. class Foo extends React.Component, lifecycle methods (componentDidMount, componentWillUnmount, shouldComponentUpdate), this.state, this.setState().

Why they faded: lifecycle methods scattered related logic across the class (data fetching split between mount and update), this binding was a perpetual footgun, and refactoring stateful logic between components was painful. Hooks (2018) solved all three.

You won't write a new class component in 2026. The official React docs no longer teach them as the primary path. If you maintain a codebase with classes, fine — but new files are function components with hooks.

Higher-order components (HOCs)

An HOC is a function that takes a component and returns a new component. The pattern (withRouter, withRedux, connect(mapStateToProps)(Component)) was the pre-hooks way to share stateful logic. You'd compose withFoo(withBar(withBaz(MyComponent))) and pray the prop types still made sense.

Why they died: the wrapping was opaque (your component disappeared inside three layers of magic), prop collisions were silent, TypeScript couldn't easily infer the result, and the wrapper chain made stack traces painful. Custom hooks replaced 99% of HOCs by being explicit about what you're consuming.

Render props

A pattern where a component takes a function as children (or another prop) and calls it with state to render. <Mouse>{(pos) => <p>{pos.x}, {pos.y}</p>}</Mouse>.

Why they died: same reason. The pattern was inventive in 2017 but a custom hook (const pos = useMousePosition()) is cleaner, type-checks better, and doesn't nest your JSX inside callbacks.

What you'll still see in real codebases

Three live exceptions you don't have to fight:

  1. React.memo — technically an HOC, but it's a tiny opt-in for memoization, not a logic-sharing pattern. Still fine to use (Track 7 covers when).
  2. Suspense wrapping any component — this is composition, not a logic-sharing HOC.
  3. Error boundaries — still class-based in React core (an error-boundary hook never landed). You'll write one class in your career; Track 5 lesson 3 shows it.
If you're rewriting an old HOC, rewrite it as a hook. The translation is almost mechanical: what the HOC injected as a prop becomes what the hook returns. The wrapping disappears. The component that was wrapped now calls the hook directly. Cleaner every time.

Code

Same feature — render prop vs custom hook·tsx
// 2018 — render prop
class Mouse extends React.Component<{ children: (p: {x:number;y:number}) => React.ReactNode }, {x:number;y:number}> {
  state = { x: 0, y: 0 };
  handle = (e: MouseEvent) => this.setState({ x: e.clientX, y: e.clientY });
  componentDidMount() { window.addEventListener("mousemove", this.handle); }
  componentWillUnmount() { window.removeEventListener("mousemove", this.handle); }
  render() { return this.props.children(this.state); }
}
// Usage — JSX inside a callback inside a tag.
<Mouse>{(pos) => <p>{pos.x}, {pos.y}</p>}</Mouse>

// 2026 — custom hook (Track 3 lesson 4 builds these properly)
import { useEffect, useState } from "react";
function useMousePosition() {
  const [pos, setPos] = useState({ x: 0, y: 0 });
  useEffect(() => {
    const handle = (e: MouseEvent) => setPos({ x: e.clientX, y: e.clientY });
    window.addEventListener("mousemove", handle);
    return () => window.removeEventListener("mousemove", handle);
  }, []);
  return pos;
}
function Tracker() {
  const pos = useMousePosition();
  return <p>{pos.x}, {pos.y}</p>;
}

External links

Exercise

Find an HOC pattern in a real-world repo (search GitHub for withRouter or connect(mapStateToProps)). Pick one and sketch how you'd convert it to a custom hook. What does the hook return? Where does the call site change? Is anything genuinely lost?
Hint
Almost always: the HOC's injected props become the hook's return value, and the component that was wrapped now calls the hook directly. The only loss is the wrapping illusion — which is a feature, not a loss.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.