Higher-Order Components (HOCs) in React

What are Higher-Order Components?

A Higher-Order Component (HOC) is an advanced pattern in React that involves a function that takes a component and returns a new enhanced component. HOCs allow you to reuse component logic, add additional props, and modify the behavior of components without modifying their implementation.

// Basic HOC pattern
function withExtraProps(WrappedComponent) {
  // Return a new enhanced component
  return function EnhancedComponent(props) {
    // Add extra props
    const extraProps = { extraData: 'Some data' };
    
    // Return the wrapped component with combined props
    return <WrappedComponent {...props} {...extraProps} />;
  };
}

// Usage
const EnhancedComponent = withExtraProps(MyComponent);

How HOCs Work

HOCs follow a simple principle: they take a component as input and return a new component with enhanced capabilities. They don’t modify the original component but compose it with new functionality.

// HOC that adds logging
function withLogging(WrappedComponent) {
  return function WithLogging(props) {
    console.log(`Component ${WrappedComponent.name} rendering with props:`, props);
    
    return <WrappedComponent {...props} />;
  };
}

// Original component
function UserProfile({ name, email }) {
  return (
    <div>
      <h2>{name}</h2>
      <p>{email}</p>
    </div>
  );
}

// Enhanced component with logging
const UserProfileWithLogging = withLogging(UserProfile);

// Usage
<UserProfileWithLogging name="John" email="john@example.com" />

Common Use Cases for HOCs

1. Code Reuse and Cross-Cutting Concerns

HOCs are excellent for sharing code between components:

// HOC for handling loading states
function withLoader(WrappedComponent, loadingMessage = 'Loading...') {
  return function WithLoader({ isLoading, ...props }) {
    if (isLoading) {
      return <div className="loader">{loadingMessage}</div>;
    }
    
    return <WrappedComponent {...props} />;
  };
}

// Usage
const UserListWithLoader = withLoader(UserList);
const ProductListWithLoader = withLoader(ProductList, 'Fetching products...');

2. Props Manipulation

HOCs can add, modify, or filter props:

// HOC that injects a theme prop
function withTheme(WrappedComponent) {
  return function WithTheme(props) {
    // Get theme from context or configuration
    const theme = useContext(ThemeContext);
    
    // Pass theme as a prop
    return <WrappedComponent {...props} theme={theme} />;
  };
}

3. State Abstraction

HOCs can manage state and pass it as props:

// HOC that manages form state
function withFormState(WrappedComponent, initialState = {}) {
  return function WithFormState(props) {
    const [formData, setFormData] = useState(initialState);
    
    const handleChange = (e) => {
      const { name, value } = e.target;
      setFormData(prevData => ({
        ...prevData,
        [name]: value
      }));
    };
    
    const handleSubmit = (e) => {
      e.preventDefault();
      console.log('Form submitted with:', formData);
      // Additional submission logic
    };
    
    return (
      <WrappedComponent
        {...props}
        formData={formData}
        handleChange={handleChange}
        handleSubmit={handleSubmit}
      />
    );
  };
}

// Usage
function LoginForm({ formData, handleChange, handleSubmit }) {
  return (
    <form onSubmit={handleSubmit}>
      <input
        name="username"
        value={formData.username || ''}
        onChange={handleChange}
        placeholder="Username"
      />
      <input
        type="password"
        name="password"
        value={formData.password || ''}
        onChange={handleChange}
        placeholder="Password"
      />
      <button type="submit">Login</button>
    </form>
  );
}

const LoginFormWithState = withFormState(LoginForm, { username: '', password: '' });

4. Conditional Rendering

HOCs can conditionally render components:

// HOC for authentication protection
function withAuth(WrappedComponent) {
  return function WithAuth(props) {
    const { isAuthenticated, user } = useAuth();
    
    if (!isAuthenticated) {
      return <Navigate to="/login" />;
    }
    
    return <WrappedComponent {...props} user={user} />;
  };
}

// Usage
const ProtectedDashboard = withAuth(Dashboard);

HOC Composition

Multiple HOCs can be composed together:

// Compose multiple HOCs
const EnhancedComponent = withAuth(withTheme(withLoader(MyComponent)));

// Using a compose utility for better readability
const enhance = compose(
  withAuth,
  withTheme,
  withLoader
);

const EnhancedComponent = enhance(MyComponent);

HOCs vs. Hooks

With the introduction of Hooks in React 16.8, many use cases for HOCs can now be handled with custom hooks:

// HOC approach
const UserProfileWithData = withUser(UserProfile);

// Hook approach
function UserProfile() {
  const user = useUser();
  
  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}

When to Use HOCs vs. Hooks:

  • Use HOCs when:

    • You need to wrap a component with additional JSX
    • You want to reuse the same enhancement across many components
    • You need to work with class components
  • Use Hooks when:

    • You want to reuse stateful logic without changing component hierarchy
    • You want to split one component into smaller functions
    • You want simpler, more readable code

Best Practices for HOCs

1. Use Descriptive Names

Name your HOCs and wrapped components clearly:

// Good
const withLoader = (WrappedComponent) => {
  function WithLoader(props) { /* ... */ }
  return WithLoader;
};

// Better: Add displayName for debugging
const withLoader = (WrappedComponent) => {
  function WithLoader(props) { /* ... */ }
  WithLoader.displayName = `WithLoader(${getDisplayName(WrappedComponent)})`;
  return WithLoader;
};

function getDisplayName(WrappedComponent) {
  return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}

2. Don’t Mutate the Original Component

Create a new component instead of modifying the input:

// Bad
function withStyles(WrappedComponent) {
  WrappedComponent.prototype.componentDidMount = function() {
    // Mutation!
  };
  return WrappedComponent;
}

// Good
function withStyles(WrappedComponent) {
  return function WithStyles(props) {
    // No mutation, just composition
    return <WrappedComponent {...props} className="styled" />;
  };
}

3. Pass Unrelated Props Through

Forward props that aren’t used by the HOC:

function withTheme(WrappedComponent) {
  return function WithTheme({ specificThemeProp, ...restProps }) {
    // Use specificThemeProp for HOC logic
    const theme = determineTheme(specificThemeProp);
    
    // Pass all other props through
    return <WrappedComponent {...restProps} theme={theme} />;
  };
}

4. Maximize Composability

Design HOCs to be easily composable with other HOCs:

// Each HOC does one thing well
const withData = (WrappedComponent) => { /* ... */ };
const withLoading = (WrappedComponent) => { /* ... */ };
const withErrorHandling = (WrappedComponent) => { /* ... */ };

// Compose them together
const enhance = compose(
  withData,
  withLoading,
  withErrorHandling
);

const EnhancedComponent = enhance(MyComponent);

Interview Tips

  • Explain that HOCs are a pattern for reusing component logic, not a React API feature
  • Discuss the difference between HOCs, render props, and hooks for code reuse
  • Mention common libraries that use HOCs (Redux’s connect, React Router’s withRouter)
  • Be prepared to discuss HOC limitations (wrapper hell, naming collisions, static methods)
  • Explain how HOCs fit into modern React development with hooks
  • Demonstrate understanding of when HOCs are still valuable despite hooks

Test Your Knowledge

Take a quick quiz to test your understanding of this topic.

Test Your React Knowledge

Ready to put your skills to the test? Take our interactive React quiz and get instant feedback on your answers.