Introduction
A Higher-Order Component is a function that takes a component and returns a new component with enhanced behavior. HOCs are a pattern for reusing component logic that predates hooks. While hooks have replaced many HOC use cases, understanding HOCs is essential for working with existing codebases and libraries that still use them.
Key Concepts
- Higher-Order Component (HOC): A function that accepts a component and returns a new component wrapping the original with additional behavior or data.
- Cross-cutting concerns: Features that affect many components, such as logging, authentication, or theming. HOCs are designed to address these.
- Convention over implementation: HOCs follow naming conventions like
withAuth,withTheme,withRouterto indicate the enhancement they provide.
Real World Context
React Router's withRouter, Redux's connect, and Relay's container components are all HOCs. A withAuth HOC wraps a page component, checks authentication status, and either renders the page or redirects to login. This keeps auth logic centralized instead of duplicated in every protected page.
Deep Dive
A basic HOC wraps a component and injects additional props:
tsxfunction withLogger<T extends object>(WrappedComponent: React.ComponentType<T>) { return function LoggedComponent(props: T) { useEffect(() => { console.log(`${WrappedComponent.displayName || 'Component'} mounted`); }, []); return <WrappedComponent {...props} />; }; } const LoggedButton = withLogger(Button);
A more practical example is a withAuth HOC:
tsxfunction withAuth<T extends object>(WrappedComponent: React.ComponentType<T>) { return function AuthenticatedComponent(props: T) { const { user, isLoading } = useAuth(); if (isLoading) return <LoadingScreen />; if (!user) return <Navigate to="/login" />; return <WrappedComponent {...props} />; }; } const ProtectedDashboard = withAuth(Dashboard);
HOCs can be composed together:
tsxconst EnhancedPage = withAuth(withTheme(withAnalytics(PageComponent)));
When to use HOCs vs hooks: Hooks are preferred for new code because they are simpler, more composable, and do not create extra wrapper components. However, HOCs remain useful when you need to wrap a component with additional JSX (conditional rendering, providers) or when working with class components.
Common Pitfalls
- Losing the display name — HOCs create wrapper components that obscure the original in React DevTools. Always set
displayNameon the returned component. - Prop name collisions — If the HOC injects a prop with the same name as one the wrapped component expects, it causes bugs. Use namespacing or unique prop names.
Best Practices
- Prefer hooks for new code — Custom hooks are simpler and avoid wrapper component nesting. Use HOCs only when hooks cannot solve the problem.
- Set displayName for debugging — Assign a descriptive displayName to the wrapper component for clear React DevTools output.
Summary
- HOCs are functions that wrap components to add behavior, following the
withXxxnaming convention. - Hooks have replaced most HOC use cases, but HOCs remain in many libraries and legacy codebases.
- When using HOCs, set displayName and watch for prop name collisions.
Code Examples
function withAuth<T extends object>(Component: React.ComponentType<T>) {
function AuthenticatedComponent(props: T) {
const { user, isLoading } = useAuth();
if (isLoading) return <Spinner />;
if (!user) return <Navigate to="/login" />;
return <Component {...props} />;
}
AuthenticatedComponent.displayName = `withAuth(${Component.displayName || Component.name})`;
return AuthenticatedComponent;
}
const ProtectedDashboard = withAuth(Dashboard);