Higher Order Components

[!NOTE] A Higher-Order Component (HOC) is an advanced technique in React for reusing component logic. Concretely, a HOC is a function that takes a component and returns a new component.

The Concept

If you’re familiar with functional programming, you might know “Higher-Order Functions” (functions that take functions as arguments or return them). HOCs are the component equivalent.

const EnhancedComponent = higherOrderComponent(WrappedComponent);

While components transform props into UI, a HOC transforms a component into another component.

Why Use HOCs?

Imagine you have multiple components that need to:

  1. Check if a user is authenticated.
  2. Log when they are mounted.
  3. Fetch data from a specific API.

Instead of copying that logic into every component, you can write it once in a HOC and wrap your components.

Interactive Demo: The withLogger HOC

This demo visualizes how a HOC wraps a component. The Button component doesn’t know anything about logging. The withLogger HOC intercepts the props, logs the click, and then passes the props down.

Implementation Example

Let’s build the withLogger HOC we just saw.

1. The HOC Function

It accepts a component (WrappedComponent) and returns a new component function.

import React, { useEffect, ComponentType } from 'react';

// Define the Props interface
interface WithLoggerProps {
  logMessage?: string;
}

function withLogger<P extends object>(WrappedComponent: ComponentType<P>) {
  // Return a new component
  return function WithLoggerComponent(props: P & WithLoggerProps) {
    useEffect(() => {
      console.log('Component mounted!');
      return () => console.log('Component unmounted!');
    }, []);

    const handleClick = () => {
      console.log('Interaction logged:', props.logMessage || 'No message provided');
    };

    // Render the wrapped component with all original props
    // We can also inject new props (like onClick interceptors)
    return (
      <div onClick={handleClick}>
        <WrappedComponent {...props} />
      </div>
    );
  };
}

2. The Wrapped Component

A simple button that knows nothing about logging.

interface ButtonProps {
  label: string;
  onClick?: () => void;
}

const SimpleButton = ({ label, onClick }: ButtonProps) => (
  <button className="btn" onClick={onClick}>
    {label}
  </button>
);

3. Usage

Wrap the component and use the enhanced version.

const ButtonWithLogger = withLogger(SimpleButton);

// Usage in App
function App() {
  return <ButtonWithLogger label="Click Me" logMessage="User clicked the button" />;
}

Visualizing HOC Wrapping

A HOC acts like a wrapper or a container around your component.

Higher Order Component Props In + Logic Wrapped Component

HOCs vs Hooks

Before React 16.8 (Hooks), HOCs were the primary way to share logic between components. Today, Hooks are generally preferred because:

  1. Less Wrapper Hell: HOCs can lead to deep component trees (“wrapper hell”), making debugging difficult.
  2. Clearer Logic: Hooks allow you to use state and other React features without writing a class.
  3. Naming Collisions: HOCs can accidentally override props if names clash.

Comparison

With HOC:

const EnhancedComponent = withWindowWidth(MyComponent);

With Hooks:

function MyComponent() {
  const width = useWindowWidth();
  return <div>Width: {width}</div>;
}

However, HOCs are still useful in specific cases, such as:

  • Integrating with third-party libraries that expect HOCs.
  • Class-based components that cannot use Hooks.
  • Conditionally rendering a component (e.g., withAuth that returns null or redirects).

Common Use Cases

  1. Authentication: withAuth(ProfilePage) - Checks if user is logged in, redirects if not.
  2. Styles/Theming: withTheme(Button) - Injects theme props.
  3. Data Fetching: withUser(Profile) - Fetches user data and passes it as a prop.
  4. Logging/Analytics: withTracking(Link) - Tracks clicks.

Caveats & Gotchas

[!WARNING] Don’t Use HOCs Inside render methods: Creating a new HOC inside render (or a function component body) will remount the component on every render, losing all its state. Create HOCs outside the component definition.

[!WARNING] Static Methods: Static methods on the wrapped component are not automatically copied to the returned component. You have to copy them manually or use libraries like hoist-non-react-statics.

[!TIP] Ref Forwarding: Refs will not be passed through automatically. You need to use React.forwardRef inside your HOC to handle refs correctly.

Summary

  • HOCs wrap components to add functionality.
  • They are great for Cross-Cutting Concerns (logging, auth, error handling).
  • They rely on Composition over Inheritance.
  • In modern React, Hooks have replaced many HOC use cases, but HOCs are still useful for class-based codebases or specific architectural patterns.