Higher-Order Components
HardA Higher-Order Component (HOC) is a function that takes a component and returns a new enhanced component. HOCs add behavior like authentication checks, data injection, or logging without modifying the original component. While largely replaced by hooks, HOCs remain in many codebases and libraries.
Interactive Visualization
Key Points
- A function that takes a component and returns a new component
- Does not modify the input component, wraps it instead
- Convention: prefix with "with" (withAuth, withLoading)
- Must forward refs and hoist statics for transparency
- Composition of multiple HOCs can create wrapper hell in the React tree
- Largely replaced by custom hooks but still common in older codebases
Code Examples
withAuth HOC
import { type ComponentType } from 'react' interface WithAuthProps { isAuthenticated: boolean userName: string } function withAuth<P extends object>( WrappedComponent: ComponentType<P> ) { function WithAuthComponent(props: Omit<P, keyof WithAuthProps>) { const isAuthenticated = useAuthStatus() const userName = useUserName() if (!isAuthenticated) { return <div>Please log in to view this page.</div> } return ( <WrappedComponent {...(props as P)} isAuthenticated={isAuthenticated} userName={userName} /> ) } WithAuthComponent.displayName = `withAuth(${WrappedComponent.displayName ?? WrappedComponent.name ?? 'Component'})` return WithAuthComponent } // Usage interface DashboardProps extends WithAuthProps { data: string[] } function Dashboard({ userName, data }: DashboardProps) { return <div>Welcome {userName}. Items: {data.length}</div> } const ProtectedDashboard = withAuth(Dashboard)
The HOC checks authentication and renders a fallback or passes auth data as injected props to the wrapped component
withLoading HOC
import { type ComponentType } from 'react' interface WithLoadingProps { isLoading: boolean } function withLoading<P extends object>( WrappedComponent: ComponentType<P> ) { function WithLoadingComponent( props: P & WithLoadingProps ) { const { isLoading, ...rest } = props as WithLoadingProps & Record<string, unknown> if (isLoading) return <div>Loading...</div> return <WrappedComponent {...(rest as P)} /> } WithLoadingComponent.displayName = `withLoading(${WrappedComponent.displayName ?? WrappedComponent.name ?? 'Component'})` return WithLoadingComponent } interface UserListProps { users: string[] } function UserList({ users }: UserListProps) { return <ul>{users.map((u) => <li key={u}>{u}</li>)}</ul> } const UserListWithLoading = withLoading(UserList) // <UserListWithLoading isLoading={true} users={[]} />
The HOC conditionally renders a loading state or the wrapped component based on the isLoading prop
Common Mistakes
- Applying HOCs inside render which creates a new component every render and destroys state
- Not setting displayName, making debugging in React DevTools difficult
- Stacking many HOCs that create deeply nested wrapper components in the tree
Interview Tips
- Compare HOCs to hooks: hooks are simpler, compose better, and do not add wrapper elements
- Know real-world examples like Redux connect() or React Router withRouter()
- Explain the problems HOCs create: wrapper hell, prop collisions, and difficult typing