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 theprops
into 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 |