A Higher-Order Component (HOC) is a function that takes a component and returns a new enhanced component. This is a design pattern in React for reusing component logic.
Think of it as a “component factory” — a function that takes a component, enhances it, and returns a new component with added features or behavior.
Why Use Higher-Order Components?
React promotes the use of composition over inheritance. HOCs are a powerful tool to abstract shared logic and extend functionality without modifying existing components.
Key benefits include:
- Code Reusability Across Components
If multiple components need the same behavior (like logging, theming, authorization), an HOC can wrap them with shared logic, reducing duplication. - Separation of Concerns
HOCs allow you to decouple logic from UI. The component focuses on rendering UI, while the HOC handles behaviors like fetching data, checking permissions, etc. - Enhancing Third-Party Components
You can wrap third-party components to add features without changing their source code — for example, adding error handling or state management. - Conditional Rendering or Logic Injection
HOCs can decide whether to render the wrapped component or something else, such as a login prompt or error message, based on props or state.
Syntax and Structure
A typical HOC looks like this:
const withEnhancement = (WrappedComponent) => {
return function EnhancedComponent(props) {
// Add custom logic here (side-effects, props manipulation, etc.)
return <WrappedComponent {...props} />;
};
};
Parameters:
WrappedComponent: The original React component you’re enhancing.props: Props passed from the parent component.EnhancedComponent: The new component returned by the HOC.
Basic Example: Logging Props
This HOC logs props every time the component renders:
function withLogger(WrappedComponent) {
return function LoggerComponent(props) {
console.log("Current props:", props);
return <WrappedComponent {...props} />;
};
}
// Usage
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
const GreetingWithLogger = withLogger(Greeting);
When you render <GreetingWithLogger name="Alice" />, it logs props and renders the original Greeting component.
Real-World Example: Access Control with Authentication
Suppose some components should only be visible to authenticated users:
function withAuth(WrappedComponent) {
return function AuthenticatedComponent(props) {
if (!props.isLoggedIn) {
return <p>You must be logged in to view this content.</p>;
}
return <WrappedComponent {...props} />;
};
}
Usage:
const Dashboard = () => <h2>Welcome to your dashboard</h2>;
const ProtectedDashboard = withAuth(Dashboard);
// Renders conditionally based on `isLoggedIn` prop
<ProtectedDashboard isLoggedIn={false} />;
Use Cases of HOCs (Expanded)
Here are common use cases where HOCs are beneficial, with more detail:
- Authentication and Access Control
Restrict access to specific components based on login status, roles, or permissions. Useful in dashboards, admin panels, etc. - Error Handling and Fallbacks
Wrap components with a try-catch-like behavior to show a fallback UI when an error occurs during rendering or data fetching. - Data Fetching and Injection
Fetch data from an API and pass it as props to the child component — for example, a list of users, or product data. - UI Enhancements
Add animations, styling themes, or feature toggles to existing components without rewriting them. - Tracking and Analytics
Automatically log views, clicks, or scroll events across wrapped components for analytics or debugging purposes.
Internals of a HOC
Here’s what happens under the hood when you use a HOC:
- The HOC does not modify the original component.
- It returns a new functional component that:
- Adds logic (e.g., modifies props, adds state)
- Renders the original component using
<WrappedComponent {...props} />
This pattern ensures immutability and reusability while providing powerful abstraction capabilities.
Best Practices for Using HOCs
- Always Copy Props to Wrapped Components
Always spread thepropsinto the wrapped component to avoid missing critical data:
<WrappedComponent {...props} />
- Use Meaningful Display Names
Helps with debugging and DevTools inspection:
const name = WrappedComponent.displayName || WrappedComponent.name || "Component";
EnhancedComponent.displayName = `withLogger(${name})`;
- Avoid Prop Collisions
Ensure that props you inject (e.g.,isAdmin,theme) don’t overwrite existing ones unless intended. - Use Composition Over Nesting
Avoid deeply nested HOCs. Instead, compose them:
export default compose(withAuth, withLogger)(MyComponent);
HOC vs Custom Hooks (Modern Alternative)
| Feature | HOC | Custom Hook |
|---|---|---|
| Form | Function wrapping a component | Function using hooks inside component |
| Output | New component | Logic for use inside a component |
| Use Case | UI enhancement, conditional rendering | Side-effects, stateful logic reuse |
| Readability | Can lead to nesting | More readable and modular |
| Preferred in modern React | ❌ Often verbose and abstract | ✅ More intuitive and concise |
