Higher-Order Components (HOCs) in React
What are Higher-Order Components?
A Higher-Order Component (HOC) is an advanced pattern in React that involves a function that takes a component and returns a new enhanced component. HOCs allow you to reuse component logic, add additional props, and modify the behavior of components without modifying their implementation.
// Basic HOC pattern
function withExtraProps(WrappedComponent) {
// Return a new enhanced component
return function EnhancedComponent(props) {
// Add extra props
const extraProps = { extraData: 'Some data' };
// Return the wrapped component with combined props
return <WrappedComponent {...props} {...extraProps} />;
};
}
// Usage
const EnhancedComponent = withExtraProps(MyComponent);
How HOCs Work
HOCs follow a simple principle: they take a component as input and return a new component with enhanced capabilities. They don’t modify the original component but compose it with new functionality.
// HOC that adds logging
function withLogging(WrappedComponent) {
return function WithLogging(props) {
console.log(`Component ${WrappedComponent.name} rendering with props:`, props);
return <WrappedComponent {...props} />;
};
}
// Original component
function UserProfile({ name, email }) {
return (
<div>
<h2>{name}</h2>
<p>{email}</p>
</div>
);
}
// Enhanced component with logging
const UserProfileWithLogging = withLogging(UserProfile);
// Usage
<UserProfileWithLogging name="John" email="john@example.com" />
Common Use Cases for HOCs
1. Code Reuse and Cross-Cutting Concerns
HOCs are excellent for sharing code between components:
// HOC for handling loading states
function withLoader(WrappedComponent, loadingMessage = 'Loading...') {
return function WithLoader({ isLoading, ...props }) {
if (isLoading) {
return <div className="loader">{loadingMessage}</div>;
}
return <WrappedComponent {...props} />;
};
}
// Usage
const UserListWithLoader = withLoader(UserList);
const ProductListWithLoader = withLoader(ProductList, 'Fetching products...');
2. Props Manipulation
HOCs can add, modify, or filter props:
// HOC that injects a theme prop
function withTheme(WrappedComponent) {
return function WithTheme(props) {
// Get theme from context or configuration
const theme = useContext(ThemeContext);
// Pass theme as a prop
return <WrappedComponent {...props} theme={theme} />;
};
}
3. State Abstraction
HOCs can manage state and pass it as props:
// HOC that manages form state
function withFormState(WrappedComponent, initialState = {}) {
return function WithFormState(props) {
const [formData, setFormData] = useState(initialState);
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prevData => ({
...prevData,
[name]: value
}));
};
const handleSubmit = (e) => {
e.preventDefault();
console.log('Form submitted with:', formData);
// Additional submission logic
};
return (
<WrappedComponent
{...props}
formData={formData}
handleChange={handleChange}
handleSubmit={handleSubmit}
/>
);
};
}
// Usage
function LoginForm({ formData, handleChange, handleSubmit }) {
return (
<form onSubmit={handleSubmit}>
<input
name="username"
value={formData.username || ''}
onChange={handleChange}
placeholder="Username"
/>
<input
type="password"
name="password"
value={formData.password || ''}
onChange={handleChange}
placeholder="Password"
/>
<button type="submit">Login</button>
</form>
);
}
const LoginFormWithState = withFormState(LoginForm, { username: '', password: '' });
4. Conditional Rendering
HOCs can conditionally render components:
// HOC for authentication protection
function withAuth(WrappedComponent) {
return function WithAuth(props) {
const { isAuthenticated, user } = useAuth();
if (!isAuthenticated) {
return <Navigate to="/login" />;
}
return <WrappedComponent {...props} user={user} />;
};
}
// Usage
const ProtectedDashboard = withAuth(Dashboard);
HOC Composition
Multiple HOCs can be composed together:
// Compose multiple HOCs
const EnhancedComponent = withAuth(withTheme(withLoader(MyComponent)));
// Using a compose utility for better readability
const enhance = compose(
withAuth,
withTheme,
withLoader
);
const EnhancedComponent = enhance(MyComponent);
HOCs vs. Hooks
With the introduction of Hooks in React 16.8, many use cases for HOCs can now be handled with custom hooks:
// HOC approach
const UserProfileWithData = withUser(UserProfile);
// Hook approach
function UserProfile() {
const user = useUser();
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}
When to Use HOCs vs. Hooks:
Use HOCs when:
- You need to wrap a component with additional JSX
- You want to reuse the same enhancement across many components
- You need to work with class components
Use Hooks when:
- You want to reuse stateful logic without changing component hierarchy
- You want to split one component into smaller functions
- You want simpler, more readable code
Best Practices for HOCs
1. Use Descriptive Names
Name your HOCs and wrapped components clearly:
// Good
const withLoader = (WrappedComponent) => {
function WithLoader(props) { /* ... */ }
return WithLoader;
};
// Better: Add displayName for debugging
const withLoader = (WrappedComponent) => {
function WithLoader(props) { /* ... */ }
WithLoader.displayName = `WithLoader(${getDisplayName(WrappedComponent)})`;
return WithLoader;
};
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}
2. Don’t Mutate the Original Component
Create a new component instead of modifying the input:
// Bad
function withStyles(WrappedComponent) {
WrappedComponent.prototype.componentDidMount = function() {
// Mutation!
};
return WrappedComponent;
}
// Good
function withStyles(WrappedComponent) {
return function WithStyles(props) {
// No mutation, just composition
return <WrappedComponent {...props} className="styled" />;
};
}
3. Pass Unrelated Props Through
Forward props that aren’t used by the HOC:
function withTheme(WrappedComponent) {
return function WithTheme({ specificThemeProp, ...restProps }) {
// Use specificThemeProp for HOC logic
const theme = determineTheme(specificThemeProp);
// Pass all other props through
return <WrappedComponent {...restProps} theme={theme} />;
};
}
4. Maximize Composability
Design HOCs to be easily composable with other HOCs:
// Each HOC does one thing well
const withData = (WrappedComponent) => { /* ... */ };
const withLoading = (WrappedComponent) => { /* ... */ };
const withErrorHandling = (WrappedComponent) => { /* ... */ };
// Compose them together
const enhance = compose(
withData,
withLoading,
withErrorHandling
);
const EnhancedComponent = enhance(MyComponent);
Interview Tips
- Explain that HOCs are a pattern for reusing component logic, not a React API feature
- Discuss the difference between HOCs, render props, and hooks for code reuse
- Mention common libraries that use HOCs (Redux’s connect, React Router’s withRouter)
- Be prepared to discuss HOC limitations (wrapper hell, naming collisions, static methods)
- Explain how HOCs fit into modern React development with hooks
- Demonstrate understanding of when HOCs are still valuable despite hooks
Test Your Knowledge
Take a quick quiz to test your understanding of this topic.