What are Higher Order Components in React and how are they used?
A Higher-Order Component is a function that takes a component and returns a new component with extra behavior — `withAuth(Component)`, `connect(mapStateToProps)(Component)` (Redux). Used for cross-cutting concerns: auth, logging, theming, data fetching. Largely replaced by custom hooks (cleaner composition, no wrapper hell). Still useful for: passing refs through, wrapping all instances uniformly, adapting class components.
HOCs are a function-as-component pattern: hoc(Component) → EnhancedComponent.
Basic HOC
function withLogger<P extends object>(Component: React.ComponentType<P>) {
return function Logged(props: P) {
useEffect(() => {
console.log('mounted', Component.displayName);
}, []);
return <Component {...props} />;
};
}
const LoggedButton = withLogger(Button);LoggedButton is a new component that renders Button plus the logging behavior.
Classic uses
Auth gate:
function withAuth<P extends object>(Component: React.ComponentType<P>) {
return function Guarded(props: P) {
const { user } = useAuth();
if (!user) return <Redirect to="/login" />;
return <Component {...props} />;
};
}
const ProtectedPage = withAuth(MyPage);Redux connect:
const ConnectedCounter = connect(
state => ({ count: state.count }),
dispatch => ({ inc: () => dispatch({ type: 'inc' }) }),
)(Counter);Theme injection (legacy):
const ThemedButton = withTheme(Button);Why hooks largely replaced HOCs
Wrapper hell. Stacking HOCs produces deeply nested trees in DevTools:
<withAuth(withTheme(withLogger(withTracking(Component))))>Prop name collisions. Multiple HOCs might inject user or theme — silent shadowing.
TypeScript pain. Generic props through multiple HOC layers is gnarly.
Hooks compose without wrapping:
function MyPage() {
const { user } = useAuth();
const theme = useTheme();
useLogger();
if (!user) return <Redirect to="/login" />;
return <View theme={theme} />;
}Same behavior, no wrappers.
When HOCs are still useful
1. Wrapping every instance uniformly.
A render-tracking HOC that wraps the export so every consumer is instrumented:
export default withErrorBoundary(MyComponent);2. Adapting class components.
If you have a class library you can't change, a HOC can inject hook-based behavior.
3. Library APIs that pre-date hooks.
react-redux connect, react-router withRouter (now deprecated). Still found in legacy code.
4. Forwarding refs through cross-cutting wrappers.
const withTheme = <P,>(Component: React.ComponentType<P>) =>
React.forwardRef<HTMLElement, P>((props, ref) => {
const theme = useTheme();
return <Component {...props} ref={ref} theme={theme} />;
});Conventions
- Name starts with
with(withAuth,withTheme). - Copy display name for DevTools:
Enhanced.displayName =withAuth(${Component.displayName})``. - Forward all props with
...props. - Don't mutate the input component; return a new one.
Alternatives
| Pattern | Use case |
|---|---|
| HOC | Library APIs pre-hooks, class adapters, uniform wrapping |
| Custom hook | Logic reuse without wrapping |
| Render props | Function-as-child for flexible UI |
| Compound components | Multi-component patterns (Tabs/Accordion) |
For new code in 2025: prefer hooks.
Senior framing
HOCs were the pre-hook way to share stateful logic. Hooks render most HOCs obsolete because logic composes via function calls without wrapping the component tree. The remaining valid uses are mostly library boundaries or class-adapter glue. New code should default to hooks.
Follow-up questions
- •Why did hooks largely replace HOCs?
- •When is a HOC still the right tool?
- •How do render props compare to HOCs and hooks?
Common mistakes
- •Stacking 5 HOCs and wondering why types are broken.
- •Mutating the input component instead of returning a new one.
- •Forgetting to forward refs through the HOC.
Performance considerations
- •Each HOC adds a wrapper component to the tree. Wrapper has its own render + reconciliation overhead. Hooks avoid the wrapper entirely.
Edge cases
- •displayName has to be copied for DevTools clarity.
- •TypeScript: generic props through HOC layers needs careful typing.
- •HOCs that conditionally render different trees can break Suspense.
Real-world examples
- •react-redux connect, react-router withRouter (deprecated), Material UI v4 withStyles (replaced by hooks in v5), Apollo Client graphql() (deprecated for useQuery).