Skip to content

Understanding React Higher-Order Components (HOCs)

Published: at 12:00 PM

React Higher-Order Components (HOCs) are a powerful tool for reusing logic across multiple components in your application. Whether you’re managing authentication, fetching data, or injecting props, HOCs can simplify and enhance your development workflow. Let’s dive into what HOCs are, how they work, and when to use them.

React Higher-Order Components


Table of Contents

Open Table of Contents

What are Higher-Order Components?

A Higher-Order Component (HOC) is a function in React that takes a component as an input and returns a new component with added functionality. It’s a design pattern for enhancing components while keeping your code modular and reusable.

Definition:

const EnhancedComponent = higherOrderComponent(WrappedComponent);

HOCs are often used for:


How Do HOCs Work?

HOCs wrap a base component, injecting additional logic or props into it. The wrapped component doesn’t need to know anything about the HOC, making it easier to manage and extend.

Example:

Let’s create an HOC that logs every render of a component:

import React from "react";

function withLogger(WrappedComponent) {
  return function EnhancedComponent(props) {
    console.log("Rendered with props:", props);
    return <WrappedComponent {...props} />;
  };
}

// Base Component
function MyComponent({ message }) {
  return <div>{message}</div>;
}

// Use the HOC
const MyComponentWithLogger = withLogger(MyComponent);

export default MyComponentWithLogger;

Whenever MyComponentWithLogger is rendered, the props are logged in the console.


When Should You Use HOCs?

HOCs are ideal when you need to reuse logic across multiple components without duplicating code. Common use cases include:


Practical Examples

1. Authentication HOC

Redirect users to a login page if they’re not authenticated.

import React from "react";
import { Navigate } from "react-router-dom";

function withAuth(WrappedComponent) {
  return function EnhancedComponent(props) {
    const isAuthenticated = !!localStorage.getItem("token"); // Example token check
    return isAuthenticated ? (
      <WrappedComponent {...props} />
    ) : (
      <Navigate to="/login" />
    );
  };
}

// Example Usage
function Dashboard() {
  return <h1>Welcome to your Dashboard!</h1>;
}

const ProtectedDashboard = withAuth(Dashboard);
export default ProtectedDashboard;

2. Data Fetching HOC

Fetch data from an API and pass it as props.

import React, { useState, useEffect } from "react";

function withData(WrappedComponent, fetchData) {
  return function EnhancedComponent(props) {
    const [data, setData] = useState(null);

    useEffect(() => {
      fetchData().then(setData);
    }, []);

    if (!data) return <div>Loading...</div>;

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

// Example Usage
function UserList({ data }) {
  return (
    <ul>
      {data.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

const fetchUsers = () =>
  fetch("https://jsonplaceholder.typicode.com/users").then(res => res.json());

const UserListWithData = withData(UserList, fetchUsers);
export default UserListWithData;

Benefits of HOCs

  1. Reusability: Share logic across multiple components.
  2. Separation of Concerns: Keeps UI components clean by offloading logic to the HOC.
  3. Extensibility: Add new features to components without modifying their code.

Drawbacks of HOCs

  1. Wrapper Hell: Nesting multiple HOCs can make debugging difficult.
    export default withLogger(withAuth(withTheme(MyComponent)));
    
  2. Performance Overhead: Each HOC adds an additional component layer to the tree.
  3. Prop Collision: Care must be taken to avoid overwriting existing props.

Alternatives to HOCs

While HOCs are powerful, modern React provides other ways to achieve similar functionality:

1. Context API

Share data globally without prop drilling.

import React, { createContext, useContext } from "react";

const UserContext = createContext();

function useUser() {
  return useContext(UserContext);
}

function App() {
  const user = { name: "Alice", age: 30 };
  return (
    <UserContext.Provider value={user}>
      <Child />
    </UserContext.Provider>
  );
}

function Child() {
  const user = useUser();
  return <div>Hello, {user.name}!</div>;
}

2. Custom Hooks

Encapsulate logic in reusable hooks.

import { useState, useEffect } from "react";

function useData(url) {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(setData);
  }, [url]);

  return data;
}

function UserList() {
  const data = useData("https://jsonplaceholder.typicode.com/users");

  if (!data) return <div>Loading...</div>;

  return (
    <ul>
      {data.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

3. Render Props

Pass functions to control what a component renders.

function DataProvider({ render }) {
  const [data, setData] = useState([]);

  useEffect(() => {
    fetch("https://api.example.com/data")
      .then(res => res.json())
      .then(setData);
  }, []);

  return render(data);
}

function MyComponent() {
  return (
    <DataProvider
      render={data => (
        <ul>
          {data.map(item => (
            <li key={item.id}>{item.name}</li>
          ))}
        </ul>
      )}
    />
  );
}

Conclusion

Higher-Order Components are a powerful tool for enhancing React components, especially when logic needs to be shared across multiple components. However, with modern React features like hooks and the Context API, consider alternatives that might be simpler and easier to manage for your use case.

Understanding when and how to use HOCs can help you write cleaner, more maintainable React applications.

Happy coding! 🚀


Previous Post
React dan Next.js Cheatsheet untuk Pemula (TypeScript dan Tailwind CSS)
Next Post
Typescript Cheatheet