Higher-Order Components (HOC) in React
A HOC is a function that takes a component and returns an enhanced one (`withFoo(Component)`). Useful for cross-cutting concerns: auth, logging, theming, data fetching. Mostly superseded by hooks (cleaner, no prop-namespace pollution, no 'wrapper hell'). Still appropriate for: HOCs that operate at the component-tree level (auth gating, error boundaries with HOC API, code splitting).
Higher-Order Components are a pre-hooks pattern for sharing behavior between components. Hooks have largely replaced them, but it's worth knowing the pattern — and when it still fits.
The shape
A HOC is a function: Component → Component.
function withAuth(Component) {
return function AuthenticatedComponent(props) {
const { user } = useAuth();
if (!user) return <Redirect to="/login" />;
return <Component {...props} user={user} />;
};
}
const Dashboard = withAuth(DashboardImpl);withRouter, connect (Redux pre-hooks), withTheme — classic HOCs.
What they did before hooks
- Share stateful logic across components (
withMouse,withForm). - Inject props from a context-like source (
connect(mapStateToProps)). - Wrap with cross-cutting concerns (logging, theming, analytics).
Why hooks replaced most of them
1. No "wrapper hell"
Stacking HOCs creates a deep tree:
withAuth(withTheme(withLogger(withRouter(Component))))The React DevTools shows 4 wrappers per component. Hooks live inside the same component:
function Component() {
const { user } = useAuth();
const theme = useTheme();
const router = useRouter();
useLogger();
// ...
}2. No prop-namespace collision
HOCs inject props. Two HOCs both injecting user clash. Hooks return values you name yourself.
3. Better composition
Composing 4 hooks reads top-to-bottom. Composing 4 HOCs reads inside-out.
4. Clearer typing
HOC types in TypeScript are notoriously hard (generics over component types). Hook types are straightforward.
When HOCs still fit
1. Tree-level concerns
When you need to wrap a component with something only React can do via composition — like an error boundary, a Suspense boundary, a route guard, or a portal:
function withErrorBoundary(Component, FallbackComponent) {
return (props) => (
<ErrorBoundary FallbackComponent={FallbackComponent}>
<Component {...props} />
</ErrorBoundary>
);
}2. Conditional rendering / redirection
Auth gating, role-based redirects:
const AdminOnly = withRole("admin")(AdminPanel);This works as a hook too (useRequireRole), but the HOC version is cleaner when the gating includes rendering a different tree.
3. Higher-Order Function on the lazy import
const LazyChart = withSuspense(lazy(() => import("./Chart")));4. Library APIs
Some libraries still expose HOCs (connect for legacy Redux usage, withRouter for older react-router, animation libs).
Building an HOC well
Pass through all props
function withFoo(Component) {
return (props) => <Component {...props} foo={getFoo()} />;
}Set a meaningful displayName
WithFoo.displayName = `WithFoo(${Component.displayName || Component.name})`;DevTools and stack traces become readable.
Hoist static methods
hoist-non-react-statics preserves a wrapped component's static methods on the wrapper.
Forward refs
If callers might attach a ref:
const WithFoo = React.forwardRef((props, ref) => <Component {...props} ref={ref} />);Compared to render props
Render props <DataProvider>{(data) => <UI data={data} />}</DataProvider> solve the same problem with a different shape — also mostly replaced by hooks.
Interview framing
"A HOC is a function that takes a component and returns an enhanced one. Pre-hooks it was the standard way to share behavior — auth, theming, data, logging. Hooks have replaced most uses because they avoid wrapper hell, prop-namespace collisions, and inside-out composition. HOCs still fit when you need to wrap a component with tree-level structure that only composition provides — error boundaries, Suspense boundaries, auth gating with redirect, lazy imports. When you build one, pass through all props, set a useful displayName, hoist statics, and forward refs. For pure logic sharing, prefer a hook."
Follow-up questions
- •Why do hooks replace most HOC use cases?
- •When is an HOC still the right tool?
- •How does an HOC differ from a render prop?
- •What's 'wrapper hell' and why does it matter?
Common mistakes
- •Reaching for HOCs in new code where a hook would do.
- •Not setting displayName — bad DevTools.
- •Not forwarding refs.
- •Prop-name collisions across stacked HOCs.
Performance considerations
- •Each HOC adds a wrapper render. Hooks have less overhead.
Edge cases
- •Library that requires an HOC API.
- •TypeScript generics over component props in an HOC.
- •HOCs that wrap an HOC (compose them with a `flow` helper).
Real-world examples
- •react-redux's `connect` (legacy).
- •react-router-v5's `withRouter`.
- •Sentry's `withErrorBoundary`.