Back to React
React
medium
mid

What are Higher Order Components in React and how are they used?

A Higher-Order Component is a function that takes a component and returns a new component with extra behavior — `withAuth(Component)`, `connect(mapStateToProps)(Component)` (Redux). Used for cross-cutting concerns: auth, logging, theming, data fetching. Largely replaced by custom hooks (cleaner composition, no wrapper hell). Still useful for: passing refs through, wrapping all instances uniformly, adapting class components.

7 min read·~5 min to think through

HOCs are a function-as-component pattern: hoc(Component) → EnhancedComponent.

Basic HOC

tsx
function withLogger<P extends object>(Component: React.ComponentType<P>) {
  return function Logged(props: P) {
    useEffect(() => {
      console.log('mounted', Component.displayName);
    }, []);
    return <Component {...props} />;
  };
}

const LoggedButton = withLogger(Button);

LoggedButton is a new component that renders Button plus the logging behavior.

Classic uses

Auth gate:

tsx
function withAuth<P extends object>(Component: React.ComponentType<P>) {
  return function Guarded(props: P) {
    const { user } = useAuth();
    if (!user) return <Redirect to="/login" />;
    return <Component {...props} />;
  };
}

const ProtectedPage = withAuth(MyPage);

Redux connect:

tsx
const ConnectedCounter = connect(
  state => ({ count: state.count }),
  dispatch => ({ inc: () => dispatch({ type: 'inc' }) }),
)(Counter);

Theme injection (legacy):

tsx
const ThemedButton = withTheme(Button);

Why hooks largely replaced HOCs

Wrapper hell. Stacking HOCs produces deeply nested trees in DevTools:

ts
<withAuth(withTheme(withLogger(withTracking(Component))))>

Prop name collisions. Multiple HOCs might inject user or theme — silent shadowing.

TypeScript pain. Generic props through multiple HOC layers is gnarly.

Hooks compose without wrapping:

tsx
function MyPage() {
  const { user } = useAuth();
  const theme = useTheme();
  useLogger();
  if (!user) return <Redirect to="/login" />;
  return <View theme={theme} />;
}

Same behavior, no wrappers.

When HOCs are still useful

1. Wrapping every instance uniformly.

A render-tracking HOC that wraps the export so every consumer is instrumented:

tsx
export default withErrorBoundary(MyComponent);

2. Adapting class components.

If you have a class library you can't change, a HOC can inject hook-based behavior.

3. Library APIs that pre-date hooks.

react-redux connect, react-router withRouter (now deprecated). Still found in legacy code.

4. Forwarding refs through cross-cutting wrappers.

tsx
const withTheme = <P,>(Component: React.ComponentType<P>) =>
  React.forwardRef<HTMLElement, P>((props, ref) => {
    const theme = useTheme();
    return <Component {...props} ref={ref} theme={theme} />;
  });

Conventions

  • Name starts with with (withAuth, withTheme).
  • Copy display name for DevTools: Enhanced.displayName = withAuth(${Component.displayName})``.
  • Forward all props with ...props.
  • Don't mutate the input component; return a new one.

Alternatives

PatternUse case
HOCLibrary APIs pre-hooks, class adapters, uniform wrapping
Custom hookLogic reuse without wrapping
Render propsFunction-as-child for flexible UI
Compound componentsMulti-component patterns (Tabs/Accordion)

For new code in 2025: prefer hooks.

Senior framing

HOCs were the pre-hook way to share stateful logic. Hooks render most HOCs obsolete because logic composes via function calls without wrapping the component tree. The remaining valid uses are mostly library boundaries or class-adapter glue. New code should default to hooks.

Follow-up questions

  • Why did hooks largely replace HOCs?
  • When is a HOC still the right tool?
  • How do render props compare to HOCs and hooks?

Common mistakes

  • Stacking 5 HOCs and wondering why types are broken.
  • Mutating the input component instead of returning a new one.
  • Forgetting to forward refs through the HOC.

Performance considerations

  • Each HOC adds a wrapper component to the tree. Wrapper has its own render + reconciliation overhead. Hooks avoid the wrapper entirely.

Edge cases

  • displayName has to be copied for DevTools clarity.
  • TypeScript: generic props through HOC layers needs careful typing.
  • HOCs that conditionally render different trees can break Suspense.

Real-world examples

  • react-redux connect, react-router withRouter (deprecated), Material UI v4 withStyles (replaced by hooks in v5), Apollo Client graphql() (deprecated for useQuery).

Senior engineer discussion

Senior framing: HOCs taught us composition-via-wrapping. Hooks gave us composition-via-call. The lesson didn't change; the ergonomics did. Recognize HOCs in legacy code; reach for hooks in new code.

Related questions