A Higher Order Component is a function that takes a component and returns an enhanced version of it — the classic pattern for cross-cutting concerns like authentication or loading states. This lesson covers how to build one, why 'wrapper hell' became a real problem, and why custom hooks replaced most of its uses.
1A Function That Takes a Component, Returns a Component
A Higher Order Component is a function that accepts a component as its argument and returns a new, enhanced component wrapping it — the same underlying idea as a higher-order function like .map() taking and returning a function, applied to components instead.
2Building withLoading
A classic HOC wraps any component, rendering a spinner while an isLoading prop is true, and otherwise rendering the wrapped component with its remaining props passed through unchanged — letting any component gain this loading behavior simply by being wrapped.
3Cross-Cutting Concerns
HOCs are particularly suited to cross-cutting concerns — behavior needed uniformly across many unrelated components, like requiring authentication or injecting a theme. Writing that logic once as a HOC and applying it by wrapping avoids duplicating the same logic across every component that needs it.
4The 'displayName' Convention
Setting displayName on the wrapper component returned by a HOC significantly improves debuggability — without it, React DevTools shows an unhelpful anonymous name for every HOC-wrapped component, making the tree confusing to navigate during debugging.
5Why HOCs Fell Out of Favor: 'Wrapper Hell'
Stacking multiple HOCs creates deeply nested wrapper components in the DevTools tree, obscuring where a given prop actually originates from. Custom hooks avoid this nesting entirely, letting multiple pieces of shared logic compose flatly within a single component, which is why most new cross-cutting logic is written as a hook rather than a HOC today.
6Step-by-Step Breakdown
A Function That Takes a Component, Returns a Component. A Higher Order Component (HOC) is a function that accepts a component as an argument and returns a new, enhanced component wrapping it. It's the same idea as a higher-order function like .map() taking a function and returning a new array — except here, both the input and output are components.
Building withLoading. A classic HOC example: withLoading wraps any component and shows a spinner while an isLoading prop is true, otherwise rendering the wrapped component with the rest of its props passed through unchanged. Any component can gain this behavior just by being wrapped.
In const UserListWithLoading = withLoading(UserList);, what does withLoading actually return?
- →A brand-new component that renders either a spinner or the wrapped component
- →The original UserList component, directly mutated in place
Cross-Cutting Concerns. HOCs shine for 'cross-cutting concerns' — behavior that applies uniformly across many unrelated components, like requiring authentication, injecting a theme, or logging every prop change. Rather than duplicating that logic in every component, you write it once and apply it by wrapping.
The 'wrapperDisplayName' Convention. Setting WithLoadingComponent.displayName = withLoading(${Component.displayName || Component.name})`` makes debugging far easier — without it, React DevTools shows an anonymous, unhelpful component name for every HOC-wrapped component, making the component tree confusing to navigate.
Why set displayName on the component returned by a Higher Order Component?
- →It makes the wrapped component identifiable in React DevTools
- →It's required for the component to render at all
Why HOCs Fell Out of Favor: 'Wrapper Hell'. Stacking several HOCs — withAuth(withTheme(withLoading(Component))) — creates deeply nested wrapper components, making the React DevTools tree hard to navigate and obscuring where a given prop actually originates. Custom hooks avoid this entirely, which is why most new cross-cutting logic is written as a hook today, not a HOC.
Mastery Achieved. You now understand Higher Order Components: functions that take a component and return an enhanced one, ideal for cross-cutting concerns like auth or logging, the displayName convention for debuggability, and why 'wrapper hell' pushed most new code toward custom hooks instead. This closes out the core pattern comparisons — next, you'll pull them together in Component Composition Patterns.
Level Up 🚀
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1HOCs Can Centralize Accessible Cross-Cutting Behavior
A HOC like withFocusTrap could wrap any modal-like component with correct focus-trapping behavior once, ensuring every wrapped component inherits consistent, correct keyboard behavior rather than reimplementing it individually.
SEO Implications
- 1
HOCs Have No Direct SEO Effect
This is a component composition pattern for client-side logic reuse, with no direct bearing on server-rendered content or crawlability by itself.
Best Practices
Always Forward Non-Related Props
A HOC should spread through any props it doesn't specifically need (`{...props}`), so the wrapped component still receives everything intended for it, rather than silently swallowing props the HOC didn't anticipate.
Set displayName on Every HOC's Returned Component
This one-line addition makes a meaningful difference in debugging a component tree that uses HOCs, especially once more than one is stacked.
Frequent Bugs
A prop passed to a HOC-wrapped component never reaches the underlying component.
The HOC's returned wrapper function likely destructures specific props but forgets to spread the rest through with {...props} to the wrapped component — ensure all unrelated props are explicitly forwarded.
React DevTools shows a long chain of anonymous, unhelpful component names for a HOC-wrapped component.
None of the stacked HOCs set displayName on their returned wrapper components. Add a displayName like `withAuth(${Component.displayName || Component.name})` to each HOC for a readable DevTools tree.
Real-World Examples
A withAuth HOC for Route Protection
Several page components need to redirect to a login screen if the user isn't authenticated, before rendering their actual content. A single withAuth HOC checks authentication status and either renders a redirect or the wrapped page component, applied uniformly across every protected route.
function withAuth(Component) {
function WithAuth(props) {
const { user } = useAuth();
if (!user) return <Navigate to="/login" />;
return <Component {...props} />;
}
WithAuth.displayName = `withAuth(${Component.displayName || Component.name})`;
return WithAuth;
}
const ProtectedDashboard = withAuth(Dashboard);