Back to React
React
easy
mid

What are Higher Order Components in React and when do you use them?

A HOC is a function that takes a component and returns an enhanced one (`withFoo(Component)`). Useful for cross-cutting concerns: auth, logging, theming, data fetching. Mostly superseded by hooks (cleaner, no prop-namespace pollution, no 'wrapper hell'). Still appropriate for: HOCs that operate at the component-tree level (auth gating, error boundaries with HOC API, code splitting).

4 min read·~15 min to think through

Higher-Order Components are a pre-hooks pattern for sharing behavior between components. Hooks have largely replaced them, but it's worth knowing the pattern — and when it still fits.

The shape

A HOC is a function: Component → Component.

jsx
function withAuth(Component) {
  return function AuthenticatedComponent(props) {
    const { user } = useAuth();
    if (!user) return <Redirect to="/login" />;
    return <Component {...props} user={user} />;
  };
}

const Dashboard = withAuth(DashboardImpl);

withRouter, connect (Redux pre-hooks), withTheme — classic HOCs.

What they did before hooks

  • Share stateful logic across components (withMouse, withForm).
  • Inject props from a context-like source (connect(mapStateToProps)).
  • Wrap with cross-cutting concerns (logging, theming, analytics).

Why hooks replaced most of them

1. No "wrapper hell"

Stacking HOCs creates a deep tree:

jsx
withAuth(withTheme(withLogger(withRouter(Component))))

The React DevTools shows 4 wrappers per component. Hooks live inside the same component:

jsx
function Component() {
  const { user } = useAuth();
  const theme = useTheme();
  const router = useRouter();
  useLogger();
  // ...
}

2. No prop-namespace collision

HOCs inject props. Two HOCs both injecting user clash. Hooks return values you name yourself.

3. Better composition

Composing 4 hooks reads top-to-bottom. Composing 4 HOCs reads inside-out.

4. Clearer typing

HOC types in TypeScript are notoriously hard (generics over component types). Hook types are straightforward.

When HOCs still fit

1. Tree-level concerns

When you need to wrap a component with something only React can do via composition — like an error boundary, a Suspense boundary, a route guard, or a portal:

jsx
function withErrorBoundary(Component, FallbackComponent) {
  return (props) => (
    <ErrorBoundary FallbackComponent={FallbackComponent}>
      <Component {...props} />
    </ErrorBoundary>
  );
}

2. Conditional rendering / redirection

Auth gating, role-based redirects:

jsx
const AdminOnly = withRole("admin")(AdminPanel);

This works as a hook too (useRequireRole), but the HOC version is cleaner when the gating includes rendering a different tree.

3. Higher-Order Function on the lazy import

jsx
const LazyChart = withSuspense(lazy(() => import("./Chart")));

4. Library APIs

Some libraries still expose HOCs (connect for legacy Redux usage, withRouter for older react-router, animation libs).

Building an HOC well

Pass through all props

jsx
function withFoo(Component) {
  return (props) => <Component {...props} foo={getFoo()} />;
}

Set a meaningful displayName

jsx
WithFoo.displayName = `WithFoo(${Component.displayName || Component.name})`;

DevTools and stack traces become readable.

Hoist static methods

hoist-non-react-statics preserves a wrapped component's static methods on the wrapper.

Forward refs

If callers might attach a ref:

jsx
const WithFoo = React.forwardRef((props, ref) => <Component {...props} ref={ref} />);

Compared to render props

Render props <DataProvider>{(data) => <UI data={data} />}</DataProvider> solve the same problem with a different shape — also mostly replaced by hooks.

Interview framing

"A HOC is a function that takes a component and returns an enhanced one. Pre-hooks it was the standard way to share behavior — auth, theming, data, logging. Hooks have replaced most uses because they avoid wrapper hell, prop-namespace collisions, and inside-out composition. HOCs still fit when you need to wrap a component with tree-level structure that only composition provides — error boundaries, Suspense boundaries, auth gating with redirect, lazy imports. When you build one, pass through all props, set a useful displayName, hoist statics, and forward refs. For pure logic sharing, prefer a hook."

Follow-up questions

  • Why do hooks replace most HOC use cases?
  • When is an HOC still the right tool?
  • How does an HOC differ from a render prop?
  • What's 'wrapper hell' and why does it matter?

Common mistakes

  • Reaching for HOCs in new code where a hook would do.
  • Not setting displayName — bad DevTools.
  • Not forwarding refs.
  • Prop-name collisions across stacked HOCs.

Performance considerations

  • Each HOC adds a wrapper render. Hooks have less overhead.

Edge cases

  • Library that requires an HOC API.
  • TypeScript generics over component props in an HOC.
  • HOCs that wrap an HOC (compose them with a `flow` helper).

Real-world examples

  • react-redux's `connect` (legacy).
  • react-router-v5's `withRouter`.
  • Sentry's `withErrorBoundary`.

Senior engineer discussion

Seniors prefer hooks for logic sharing, reach for HOCs only when tree-level wrapping is the natural shape, and build HOCs correctly (displayName, forwardRef, hoist statics, prop passthrough).

Related questions