Higher-Order Components

Hard

A 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

Related Concepts