Back to issues
REACT

Higher Order Components in React: Why They Still Matter (and When to Use Hooks)

HOCs were the dominant pattern in React for years. Understand how they work, when they still make sense, and why Hooks changed the game.

By Thiago Saraiva7 MIN

If you've been working with React for a while, you've probably seen code like export default withAuth(withTheme(withRouter(MyComponent))). That tower of wrappers is the result of Higher Order Components, a pattern that dominated React before Hooks arrived.

But HOCs didn't die. Knowing how they work and when to reach for them is still relevant.

Mental model: A HOC is like wrapping a gift inside another gift box, then another, then another. The original present is untouched, but the receiver has to peel through layers to get to it. Hooks skip the boxes entirely. You open the drawer and grab what you need.

What Is a HOC?

A Higher Order Component is a function that takes a component and returns a new enhanced component. It's composition, not inheritance:

The original component doesn't know it was wrapped. It just receives extra props. Think of it as a decorator around a function, except the "function" renders UI.

Practical Example: Authentication

The classic use case. You want to protect pages without repeating logic:

Picture a bouncer at the door. The component inside doesn't care about the ID check. It just starts the party once the bouncer nods.

Data Fetching with HOC

Another classic: abstracting data fetching logic:

Notice the currying: withData(url)(Component). The URL is the configuration, the component is what receives the data.

Wrapper Hell, Visualized

Before (HOC stack):

<Provider>
  <ThemeProvider>
    <Router>
      <AuthGate>
        <AnalyticsTracker>
          <Dashboard />   <-- the actual component, 5 layers deep
        </AnalyticsTracker>
      </AuthGate>
    </Router>
  </ThemeProvider>
</Provider>

After (Hooks):

function Dashboard() {
  useTheme();
  useRouter();
  useAuth();
  useAnalytics();
  return <DashboardUI />;  // flat, explicit, debuggable
}

Same behavior. One is a Russian nesting doll, the other is a shopping list.

The Pitfalls of HOCs

  1. displayName: Without it, React DevTools shows anonymous components. Always define Component.displayName.
  2. Refs don't pass through automatically: Use React.forwardRef if you need to reach the inner component's ref.
  3. Static props get lost: Use hoist-non-react-statics to copy static methods across.
  4. Never create a HOC inside render: The component gets recreated on every render, destroying state and tanking performance.

The Modern Alternative: Hooks

Hooks do the same job with less ceremony:

No wrapper hell. No magic props appearing out of nowhere. The reusable logic is explicit in the component.

War Story: The connect() to useSelector Migration

Back in the react-redux 7.1 days (mid-2019, when the Hooks API for react-redux landed), every container component looked like this: export default connect(mapStateToProps, mapDispatchToProps)(MyComponent). On one project, a single screen had connect() wrapping withRouter wrapping withTranslation wrapping the actual component. Four layers, and every time somebody added a Redux slice, mapStateToProps grew another paragraph.

We migrated to useSelector and useDispatch over a sprint. Same logic, roughly 40% less boilerplate, and the React DevTools tree went from a matryoshka of Connect(withRouter(...)) into actual component names. Apollo Client told the same story: the graphql() HOC from react-apollo gave way to useQuery and useMutation in Apollo Client 3, and most teams I know retired the HOC entirely. The lesson: when the wrapper exists only to inject data, a Hook is almost always cleaner.

HOC vs Hooks vs Render Props: Decision Matrix

NeedHOCHooksRender Props
Share stateful logicOKBestOK
Gate rendering (auth, redirect)BestOKOK
Inject props from external sourceGoodBestOK
Works with class componentsYesNoYes
Type inference (TypeScript)PainfulEasyMedium
Debugging in DevToolsNoisy treeCleanVerbose JSX
Compose multiple behaviorsNestingFlatNesting

So When Should You Still Use HOCs?

  • Legacy libraries that expose HOCs (some state connectors, older form libraries, instrumentation SDKs).
  • Cross-cutting concerns that need to wrap components without touching them (analytics instrumentation, programmatic error boundaries).
  • Conditional rendering decisions based on logic, like redirecting or showing a fallback, not just returning data.

FAQ

1. How do HOCs play with TypeScript? Typing <P extends object>(Component: ComponentType<P>) => ComponentType<P & InjectedProps> works, but generics get ugly fast, especially when you compose multiple HOCs. Custom Hooks infer types automatically from their return value. If TypeScript is in your stack, that alone is a reason to prefer Hooks.

2. What about refs? Do I need forwardRef? With React 18 and earlier, yes. A HOC breaks the ref chain by default because the ref attaches to the wrapper, not the inner component, so you wrap the returned component in React.forwardRef and pass the ref down explicitly. React 19 simplifies this: function components can accept ref as a regular prop, so the ceremony shrinks. Hooks sidestep the issue entirely since there's no wrapper.

3. Do React Server Components break HOCs? They can. Server Components don't support state or effects, so any HOC using useState or useEffect only works inside the client boundary (a 'use client' file). You can still write pure prop-injecting HOCs that run on the server, but the pattern feels forced compared to just composing async server components directly. As of 2026, with the App Router stable and RSC the default in Next.js, the practical answer is: keep HOCs on the client, lean on async server components and Suspense on the server.

4. Does a custom Hook always replace a HOC? No. Hooks can't decide whether to render something else. If your HOC returns <Redirect /> or <AccessDenied /> instead of the wrapped component, a Hook alone won't do that. You'd need the Hook plus an early return inside the component. For pure data and logic sharing, a Hook replaces the HOC almost every time.

5. Is testing different for HOCs versus Hooks? HOCs get tested as components: render the wrapped output and assert on props. Hooks get tested with @testing-library/react's renderHook, which is more direct and doesn't need a dummy component. Hooks win on ergonomics here too.

Key Takeaways

HOCs are a valid pattern, and understanding the mechanics behind them sharpens your sense of composition in React. But for most modern use cases, Hooks are simpler, more explicit, and easier to debug. Reach for HOCs when the situation calls for it (legacy interop, render gating, cross-cutting wrapping). Make Hooks the default.