Skip to main content

Higher-Order Components

What is a HOC?

A Higher-Order Component is a function that takes a component and returns a new component with additional props or behavior. Follows the withX naming convention.

function withAuth(WrappedComponent) {
return function AuthenticatedComponent(props) {
const { user, isLoading } = useAuth();

if (isLoading) return <LoadingSpinner />;
if (!user) return <Redirect to="/login" />;

return <WrappedComponent {...props} user={user} />;
};
}

// Usage
const ProtectedDashboard = withAuth(Dashboard);

Common HOC Patterns

withLoading

function withLoading(Component) {
return function WithLoadingComponent({ isLoading, ...props }) {
if (isLoading) return <Spinner />;
return <Component {...props} />;
};
}

withErrorBoundary

function withErrorBoundary(Component, FallbackUI) {
return class WithErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError() { return { hasError: true }; }
componentDidCatch(error, info) { logError(error, info); }

render() {
if (this.state.hasError) return <FallbackUI />;
return <Component {...this.props} />;
}
};
}

withLogger (Analytics)

function withLogger(Component, eventName) {
return function LoggedComponent(props) {
useEffect(() => {
analytics.track(`${eventName}_mounted`);
return () => analytics.track(`${eventName}_unmounted`);
}, []);

return <Component {...props} />;
};
}

Problems with HOCs

1. Wrapper Hell — Hard to Debug

export default withAuth(withLoading(withLogger(withTheme(Dashboard))));
// DevTools shows: withTheme(withLogger(withLoading(withAuth(Dashboard))))
// Hard to see which HOC is causing issues

2. Props Collision

// Both HOCs inject a `data` prop — later one silently overwrites
const Enhanced = withUserData(withProductData(Component));
// Component receives only one `data`!

3. TypeScript Complexity

Properly typing composed HOCs requires advanced generics.

4. Ref Forwarding

Refs don't automatically work through HOCs — must add React.forwardRef.

Modern Alternative: Custom Hooks

// ❌ HOC approach: wrapper component
function withWindowSize(Component) {
return function(props) {
const size = useWindowSize();
return <Component {...props} windowSize={size} />;
};
}

// ✅ Hook approach: no wrapper, no collision
function Dashboard() {
const { width, height } = useWindowSize();
const { user } = useAuth();
const { theme } = useTheme();
// All explicit, no hidden dependencies
}

When HOCs Still Make Sense

  • Error Boundaries — can't implement as hooks yet
  • Wrapping third-party components (class-based libraries)
  • Legacy class component codebases
  • Library implementations (Redux's connect, React Router's withRouter)

HOC vs Hook Decision

Use HOCUse Hook
Error boundariesAny other logic sharing
Wrapping class componentsFunction components
Library code (connect, withRouter)Application code
Adding render behaviorAdding state/effect logic

Modern best practice (2024+): Hooks for almost everything. HOCs for error boundaries and legacy code.


Content from Frontend-Master-Prep-Series03-react