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:
- Check if a user is authenticated.
- Log when they are mounted.
- 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.
HOCs vs Hooks
Before React 16.8 (Hooks), HOCs were the primary way to share logic between components. Today, Hooks are generally preferred because:
- Less Wrapper Hell: HOCs can lead to deep component trees (“wrapper hell”), making debugging difficult.
- Clearer Logic: Hooks allow you to use state and other React features without writing a class.
- 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.,
withAuththat returnsnullor redirects).
Common Use Cases
- Authentication:
withAuth(ProfilePage)- Checks if user is logged in, redirects if not. - Styles/Theming:
withTheme(Button)- Injects theme props. - Data Fetching:
withUser(Profile)- Fetches user data and passes it as a prop. - 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.forwardRefinside 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.