DEV Community

nagendra babu
nagendra babu

Posted on

The Evolution of React Reusability Patterns - From Composition to Custom Hooks

TL;DR — React reusability evolved through four patterns: Composition → HOC → Render Props → Custom Hooks. Each pattern solved the problems of the previous one. Today, Custom Hooks for logic and Composition for UI is the recommended approach.

Introduction

Every React developer eventually hits the same problem:

I wrote this logic in one component… now I need it in five more.

Over the years, the React community developed several patterns to solve this problem. Some worked well. Some created new problems.
The journey of React reusability went through four major patterns:

Component Composition
        ↓
Higher Order Components (HOC)
        ↓
Render Props
        ↓
Custom Hooks ✅
Enter fullscreen mode Exit fullscreen mode

In this blog, we will walk through all four patterns — not just what they are, but why they exist and when to use each one.
By the end, you will have a clear mental model for choosing the right pattern in any situation.

1️⃣ Component Composition — UI Reuse

React's original philosophy is:

Composition over inheritance

Instead of creating large monolithic components, React encourages building small reusable components that can be combined together.

Example

function Card({ children }) {
  return <div className="card">{children}</div>;
}

Enter fullscreen mode Exit fullscreen mode

Usage

<Card>
  <UserList />
</Card>

<Card>
  <ProductList />
</Card>

<Card>
  <h2>Any content I want</h2>
  <Button>Click me</Button>
</Card>

Enter fullscreen mode Exit fullscreen mode

Evaluation

Composition works very well for:

  • reusable UI

  • layout flexibility

  • simple component structure

However, it does not address logic reuse.

For example

  • data fetching

  • authentication checks

  • analytics tracking

When multiple components needed the same logic, developers needed another solution.

2️⃣ Higher Order Components — Logic Reuse via Wrapping

Higher Order Components were introduced to reuse behavior across components.
A HOC is a function that takes a component and returns a new enhanced component.

Example

function withAuth(Component) {
  return function WrappedComponent(props) {
    const user = useAuth();

    if (!user) return <Login />;
    return <Component {...props} />;
  };
}
Enter fullscreen mode Exit fullscreen mode

Usage

const ProtectedDashboard = withAuth(Dashboard);
const ProtectedProfile = withAuth(Profile);
const ProtectedSettings = withAuth(Settings);
Enter fullscreen mode Exit fullscreen mode

Evaluation

HOCs allowed developers to reuse logic such as:

  • authentication
  • analytics
  • permissions
  • logging

But they introduced new problems.

Wrapper Hell

withAuth(
  withTheme(
    withTracking(
      withPermissions(MyComponent)
    )
  )
)
Enter fullscreen mode Exit fullscreen mode

Other issues included:

  • difficult debugging — anonymous components in React DevTools
  • prop collisions — two HOCs passing a prop with the same name
  • complex component trees — hard to trace where props come from

Developers wanted a more flexible solution.

3️⃣ Render Props — Logic Reuse via Functions

Render Props was introduced to solve the wrapper hell problem of HOCs.

Instead of wrapping a component, a Render Prop passes a function as a prop. The component calls that function with data, and the parent decides what to render.

Example

function MouseTracker({ render }) {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  const handleMouseMove = (e) => {
    setPosition({ x: e.clientX, y: e.clientY });
  };

  return (
    <div onMouseMove={handleMouseMove}>
      {render(position)}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Usage

<MouseTracker
  render={({ x, y }) => (
    <h1>Mouse is at {x}, {y}</h1>
  )}
/>

<MouseTracker
  render={({ x, y }) => (
    <Circle cx={x} cy={y} />
  )}
/>
Enter fullscreen mode Exit fullscreen mode

Evaluation

Render Props solved wrapper hell by giving consumers full control over rendering.
Benefits included:

  • no component wrapping
  • no prop collisions
  • flexible UI rendering

However, deeply nested Render Props created a new problem — Callback Hell

<MouseTracker
  render={({ x, y }) => (
    <AuthTracker
      render={({ user }) => (
        <ThemeTracker
          render={({ theme }) => (
            <Dashboard x={x} y={y} user={user} theme={theme} />
          )}
        />
      )}
    />
  )}
/>
Enter fullscreen mode Exit fullscreen mode

Other issues included

  • hard to read
  • messy JSX structure
  • performance concerns due to inline functions re-creating on every render

Developers needed a cleaner solution that lived outside JSX entirely.

4️⃣ Custom Hooks — Logic Reuse the React Way

Custom Hooks were introduced in React 16.8 with the Hooks API.
They allow developers to extract stateful logic into a plain JavaScript function — completely separate from any component or JSX.

Any function that starts with use and calls other React hooks is a Custom Hook.

Example

function useMousePosition() {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  useEffect(() => {
    const handleMouseMove = (e) => {
      setPosition({ x: e.clientX, y: e.clientY });
    };
    window.addEventListener("mousemove", handleMouseMove);
    return () => window.removeEventListener("mousemove", handleMouseMove);
  }, []);

  return position;
}
Enter fullscreen mode Exit fullscreen mode

Usage

function App() {
  const { x, y } = useMousePosition();
  return <h1>Mouse is at {x}, {y}</h1>;
}

function Circle() {
  const { x, y } = useMousePosition();
  return <circle cx={x} cy={y} />;
}
Enter fullscreen mode Exit fullscreen mode

Composing Multiple Hooks

function Dashboard() {
  const { x, y } = useMousePosition();
  const user = useAuth();
  const theme = useTheme();
  const { data, loading } = useFetch("/api/data");

  if (loading) return <Spinner />;

  return <div>...</div>;
}
Enter fullscreen mode Exit fullscreen mode

Clean. Flat. Readable. ✅

Evaluation

Custom Hooks solved every problem introduced by the previous patterns:

  • no wrapper hell
  • no prop collisions
  • no messy JSX
  • easy to test in isolation
  • easy to compose multiple hooks together
  • logic lives completely outside the component

When to Use What

Need to reuse UI layout?
  → Use Composition

Need to reuse logic?
  → Use Custom Hooks

Working with an older codebase?
  → You will likely see HOCs and Render Props
  → Understand them, but prefer Custom Hooks for new code

Enter fullscreen mode Exit fullscreen mode

Conclusion

React's reusability story is one of the best examples of a community-driven evolution in frontend development.

Nobody sat down and planned all four patterns upfront. Each one emerged from real frustration developers faced at scale — and each one made React applications a little cleaner and more maintainable than before.

Here is the simple mental model to take away:

💡 Use Composition when you want to reuse UI.

💡 Use Custom Hooks when you want to reuse logic.

HOCs and Render Props served their purpose well. You will still encounter them in older codebases and popular libraries like React Router and React Query. Understanding them makes you a stronger React developer.

But for new code written today — Custom Hooks is the clear winner.

React did not get here overnight. It took years of real-world experience, community feedback, and a willingness to evolve.
And honestly? That is what makes React so powerful.

Which pattern do you still use most in your codebase? Drop a comment below! 👇

Top comments (0)