Higher-Order Components
A higher-order component, or HOC, is a function that takes a component and returns a new component.
const EnhancedComponent = withSomething(BaseComponent);HOCs were especially common before hooks. You still see them in older React code and some libraries.
Basic Example
function withLoading(Component) {
return function WithLoading({ isLoading, ...props }) {
if (isLoading) {
return <p>Loading...</p>;
}
return <Component {...props} />;
};
}Usage:
const UserListWithLoading = withLoading(UserList);
<UserListWithLoading isLoading={isLoading} users={users} />The HOC adds loading behavior without changing UserList.
HOCs Share Behavior
A HOC can inject props.
function withCurrentUser(Component) {
return function WithCurrentUser(props) {
const user = useCurrentUser();
return <Component {...props} currentUser={user} />;
};
}This is useful when several components need the same data.
In modern React, a custom hook is often simpler:
function ProfileMenu() {
const currentUser = useCurrentUser();
return <Menu user={currentUser} />;
}Do Not Mutate the Wrapped Component
Avoid changing the component you receive.
// Bad
function withDebug(Component) {
Component.defaultProps = { debug: true };
return Component;
}Return a new component instead.
function withDebug(Component) {
return function WithDebug(props) {
return <Component {...props} debug />;
};
}Prop Collisions
HOCs can accidentally overwrite props.
function withTheme(Component) {
return function WithTheme(props) {
return <Component {...props} theme="dark" />;
};
}If the caller also passes theme, this HOC forces "dark".
Be deliberate about prop precedence.
return <Component theme="dark" {...props} />;Now the caller can override the default.
Display Names
Wrapped components can be harder to debug.
Set a display name when useful.
function withTracking(Component) {
function WithTracking(props) {
return <Component {...props} />;
}
WithTracking.displayName = `withTracking(${Component.displayName || Component.name || "Component"})`;
return WithTracking;
}This makes React DevTools easier to read.
Refs and HOCs
Refs do not automatically pass through HOCs.
const TrackedInput = withTracking(TextInput);If a parent passes a ref to TrackedInput, the ref points to the wrapper unless you explicitly forward it.
function withTracking(Component) {
return React.forwardRef(function WithTracking(props, ref) {
trackRender(Component.name);
return <Component {...props} ref={ref} />;
});
}Only forward refs when that is part of the wrapped component's API.
Common HOC Use Cases
You may see HOCs for:
- routing data in older router versions
- connecting to state stores
- analytics tracking
- permission checks
- feature flags
- data loading in older code
Common Mistakes
- Creating HOCs when a custom hook would be clearer.
- Forgetting to pass props through.
- Accidentally overwriting caller props.
- Losing refs.
- Wrapping components inside render, which creates a new component type every render.
function Page() {
const Enhanced = withLoading(UserList); // avoid doing this inside render
return <Enhanced />;
}Create the enhanced component outside render instead.
What is a higher-order component?
Recap
HOCs are a legacy-friendly pattern for reusing component behavior.
Understand them so you can maintain existing React code. For new code, compare them with custom hooks and composition before adding another wrapper.
Practice
Write a withPermission HOC that renders an "Access denied" message unless the user has a required role.
Then rewrite the same behavior using a custom hook and compare the two APIs.