Higher Order Components in React: Examples with Common Use-case Patterns

Higher Order Components in React: Examples with Common Use-case Patterns

·

11 min read

Dead Simple Chat offers powerful Javascript Chat SDK and APIs that you can use to easily add Chat functionality to your React Applications.

In this blog post, we will learn what are Higher Order Components, and learn about common use-case patterns. We will also look at code examples of common use-case patterns using Higher Order Components.

What are HOCs in React?

Higher Order Components or HOC are basically functions that take a component as an argument and return a new component.

Higher Order Components are useful to abstract a shared logic used by many components.

Components in React allow code reuse and prevent code repetition.

But when you develop a React application it is not always straightforward to apply the DRY principles and you have to write repeated code in multiple components.

But by using HOCs you can reduce the repeated code, by encapsulating that logic into a Higher Order Component. We will look at some examples and patterns below.

Dead Simple Chat offers powerful Javascript Chat SDK and APIs that you can use to easily add Chat functionality to your React Applications.

Basic HOC Example

Let's look at a very basic example, to understand Higher Order Components.

This is a very basic example and in the real world it will not make sense to encapsulate the logic into Higher Order Components but it would serve as a good example to understand Higher Order Components.

We will also look at examples and scenarios later in this blog post where to use Higher Order Components in real-world use cases.

Let's look at our example, we will create a React Component that upon hover its background color changes to blue.

const TextComponent = ({ text }) => {
  const [isHovered, setHovered] = useState(false);

  function handleMouseEnter() {
    setHovered(true);
  }

  function handleMouseLeave() {
    setHovered(false);
  }

  return (
    <>
      <p
        style={{ backgroundColor: isHovered ? "blue" : "white" }}
        onMouseEnter={handleMouseEnter}
        onMouseLeave={handleMouseLeave}
      >
            { text }
      </p>
    </>
  );
};

We have created a simple React Componenet called as the TextComponent and when the mouse is hovered upon the TextComponent its background color changes to blue.

To achieve this we are using the isHovered variable and setting it to true on the MouseEnter event and then resetting it to false on MouseLeave event.

However, if we want to add this same functionality into other components in our application, then we would have to repeat the same code, again in the other components.

To prevent this we can wrap the logic to detect the hover in a Higher Order Component, lets see an updated example using Higher Order Components.

// Higher Order Component that Contians the logic
// to detect the hover.
function withHover(WrappedComponent) {
  return function (props) {
    const [isHovered, setHovered] = useState(false);

    function handleMouseEnter() {
      setHovered(true);
    }

    function handleMouseLeave() {
      setHovered(false);
    }

    return (
      <div onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
        <WrappedComponent {...props} isHovered={isHovered} />
      </div>
    );
  };
}

// Updated Text Component without the Hover Logic
const TextComponent = ({ text, isHovered }) => {
  return (
    <>
      <p style={{ backgroundColor: isHovered ? "blue" : "white" }}>{text}</p>
    </>
  );
};

// Updated Input Component without the Hover Logic
const InputComponent = ({ type, isHovered }) => {
  return (
    <input
      type={type}
      style={{ backgroundColor: isHovered ? "blue" : "white" }}
    />
  );
};

// Creating components that contain hover logic using
// Higher Order Component.
const TextComponentWithHover = withHover(TextComponent);
const InputComponentWithHover = withHover(InputComponent);

// Using the Components in our App
const App = () => {
  return (
    <div className="App">
      <TextComponentWithHover
        text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
        tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
        veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
        commodo consequat."
      />

      <InputComponentWithHover type="text" />
    </div>
  );
};

export default App;

In the above code example, we have created a Higher Order Component named withHover.

In the withHover Higher Order Component, we are handling the MouseEnter and MouseLeave events and setting the value of isHovered to true when there is a hover over the componenet.

We are also passing the isHovered as a prop to the WrappedComponent and we use this prop to handle the hover condition in the WrappedComponents.

We have also updated the TextComponent and the code of the TextComponent has become much smaller. In the TextComponent we are just checking if the isHover the prop is set to true and if it is set to true then we are setting the background color to blue.

Dead Simple Chat offers powerful Javascript Chat SDK and APIs that you can use to easily add Chat functionality to your React Applications.

Common Use Case Patterns for HOCs

Now let's discuss some common use case patterns where using Higher Order Components would be beneficial.

1. Management of State and Handling of Async Data

Higher order components can be used to manage state and perform async data retrieval and normal components would be focused on displaying the data.

This results in the creation of loosely coupled components with a clear separation of roles. Let's look at an example:

Example:

import "./styles.css";
import { useState, useEffect } from "react";

function withDataFetch(WrappedComponent, fetchData) {
  return function (props) {
    const [data, setData] = useState(null);
    const [isLoading, setIsLoading] = useState(true);
    const [error, setError] = useState(null);

    useEffect(() => {
      const fetchWrapper = async () => {
        try {
          const data = await fetchData(props);
          setData(data);
          setIsLoading(false);
        } catch (error) {
          setError(error);
          setIsLoading(false);
        }
      };
      fetchWrapper();
    }, [props]);

    return (
      <WrappedComponent
        {...props}
        data={data}
        isLoading={isLoading}
        error={error}
      />
    );
  };
}

const fetchPosts = async () => {
  const response = await fetch("https://jsonplaceholder.typicode.com/posts");
  const data = await response.json();
  return data;
};

const PostComponent = ({ data, isLoading, error }) => {
  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

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

const PostComponentWithData = withDataFetch(PostComponent, fetchPosts);

export default function App() {
  return (
    <div className="App">
      <PostComponentWithData />
    </div>
  );
}

In the above code example, we have created a Higher Order Component called as withDataFetch. This HOC accepts a component and a method to fetch the data as an argument.

The HOC contains the logic to call the fetchData method to retrieve the data, pass the data to the WrappedComponent. It also passes the isLoading and error state to the WrappedComponent so that the componenet can display appropriate UI to the user.

Then we create our PostComponent. The PostComponet displays the data in a list and also checks for error and isLoading props and display appropriate messages.

Finally, we create our fetchPosts method, which calls the jsonplaceholder.typicode.com/posts API to fetch a list of mock posts.

We are then calling our HOC, and passing our PostComponent and our data fetch method fetchPosts and generating a wrapped component PostComponentWithData and adding it to our app.

And the PostComponetWithData displays a list of Posts:

With Dead Simple Chat you can easily add Chat Functionality to your application. Powerful JavaScript Chat SDK provided by Dead Simple Chat allows you to add chat in minutes.

2. Conditional Rendering: Authorization and Feature Toggle

Higher Order Components can also be used to show components only if they meet a condition.

For e.g: A set of components should only be displayed to authorized users or experimental features should be enabled only if the feature flag is set.

So, using Higher Order Components we can abstract away the logic to fetch the info if the componenet should be shown or not and reuse it in multiple components.

Example 1: Authorization

Let's look at an example below:

import "./styles.css";

// Higher Order Component
function withAuthorization(WrappedComponent, checkPermissions) {
  return function (props) {
    // You can also wrap it in useEffect for async permission checks.
    return checkPermissions(props) ? (
      <WrappedComponent {...props} />
    ) : (
      <p>Please login with appropriate role</p>
    );
  };
}

const PrivateComponent = () => {
  return <div>This is a Private Component, only visible to Admin Users.</div>;
};

const checkPermission = (props) => {
  return props.userRole === "admin";
};

const PrivateComponentComponentWithAuthorization = withAuthorization(
  PrivateComponent,
  checkPermission
);

export default function App() {
  return (
    <div className="App">
      <PrivateComponentComponentWithAuthorization userRole="admin" />

      <PrivateComponentComponentWithAuthorization userRole="guest" />
    </div>
  );
}

In the above code, we have created a Higher Order Component called as withAuthorization.

The HOC accepts a WrappedComponent and the method called as checkPermissions to check if the user has permission to view the WrappedComponenet

Right now in our example, we are assuming the checkPermission method is synchronous, but this example can also be easily modified to be used with async permission checking by using the useEffect hook.

To learn how to use the useEffect hook in the HOCs see the previous fetchPosts example.

Next, we created a componenet called the PrivateComponent. The PrivateComponent has no logic for checking the user permission, this component should only be displayed to admin users.

Then finally we create a very basic method to check the permission called the checkPermission method. This method just checks if the userRole is admin.

Example 2: Feature Toggle

Here is an example to add a feature toggle using Higher-order components:

import "./styles.css";
import React from "react";

function withFeatureToggle(WrappedComponent, featureEnabled) {
  return function (props) {
    return featureEnabled ? <WrappedComponent {...props} /> : <></>;
  };
}

const NewFeatureComponent = () => {
  return (
    <div>
      <button>New Feature</button>
    </div>
  );
};

// Global Feature Toggle.
const enableNewFeatures = false;

const NewFeatureComponentWithFeatureToggle = withFeatureToggle(
  NewFeatureComponent,
  enableNewFeatures
);

export default function App() {
  return (
    <div className="App">
      <NewFeatureComponentWithFeatureToggle />
    </div>
  );
}

We have created a global feature toggle called as enableNewFeatures when it set to true the component NewFeatureComponent will be displayed otherwise it will be hidden.

Dead Simple Chat offers powerful Javascript Chat SDK and APIs that you can use to easily add Chat functionality to your React Applications.

3. Translation and Language Switching

Higher-order components can also be used to add Translation, internationalization and the ability to easily switch between languages.

Let's look at an example:

import "./styles.css";

import React, { useState } from 'react';

const i18n = {
  en: {
    "Please Login": 'Please Login',
  },
  es: {
    "Please Login": 'Por favor Iniciar sesión',
  },
  fr: {
    "Please Login": 'Veuillez vous connecter'
  }
};

function withTranslation(WrappedComponent, translation) {
  return function (props) {
    const [language, setLanguage] = useState('en');

    const translate = (key) => translation[language][key] || key;

    const changeLanguage = (lang) => {
      setLanguage(lang);
    };

    return (
      <WrappedComponent
        {...props}
        t={translate}
        language={language}
        changeLanguage={changeLanguage}
      />
    );
  };
}


const LoginComponent = ({ t, language, changeLanguage }) => {
  return (
    <div>
      <p>{t('Please Login')}</p>
      <p>Current language: {language}</p>
      <button onClick={() => changeLanguage('en')}>English</button>
      <button onClick={() => changeLanguage('es')}>Español</button>
      <button onClick={() => changeLanguage('fr')}>Français</button>

    </div>
  );
};

const LoginComponentWithTranslation = withTranslation(LoginComponent, i18n);


export default function App() {
  return (
    <div className="App">
    <LoginComponentWithTranslation />
    </div>
  );
}

Let's understand the above example. We have created an object called as i18n that contain the translations for 3 languages.

Then we are creating a higher-order component called as withTranslation that accepts a component and transitions as an argument.

The withTranslation HOC returns a sub-object of the i18n object based on the language selected by the user. It also offers a method to change the selected language.

We are then creating our LoginComponent, the LoginComponent accepts the props from the Higher Order Component, which include the translations stored in the prop t , currently selected language in the prop language and changeLanguage method.

Finally, we are creating a component called LoginComponentWithTranslation using our HOC that allows us to easily switch between languages.

Dead Simple Chat offers powerful Javascript Chat SDK and APIs that you can use to easily add Chat functionality to your React Applications.

4. Error Boundary

Higher Order Components can also be used to handle un-handeled errors using Error Boundaries. Once caveat is that the Error Boundary is not supported in functional components.

Hence, only the HOC has to be the class componenet, the WrappedComponent can be a functional component.

Let's check out an example of Error Boundary using and HOC:

import React, { Component } from "react";

function withErrorBoundary(WrappedComponent) {
  return class ErrorBoundary extends Component {
    constructor(props) {
      super(props);
      this.state = { hasError: false, error: null };
    }

    static getDerivedStateFromError(error) {
      return { hasError: true, error };
    }

    componentDidCatch(error, errorInfo) {
      console.error("Error handled by ErrorBoundary:", error, errorInfo);
    }

    render() {
      if (this.state.hasError) {
        return <p>Something went wrong: {this.state.error.message}</p>;
      }

      return <WrappedComponent {...this.props} />;
    }
  };
}

const DataViewerComponent = ({ data }) => {
  if (!data) {
    throw new Error("Please specify the data");
  }

  return (
    <div>
      <h2>Data:</h2>
      {data}
    </div>
  );
};

const DataViewerWithErrorBoundary = withErrorBoundary(DataViewerComponent);

export default function App() {
  return (
    <div className="App">
      <DataViewerWithErrorBoundary />
    </div>
  );
}

In the above code we have created a HOC withErrorBoundary that returns a class component that wraps and Error Boundary around the WrappedComponent.

Whenever an exception occurs the getDerivedStateFromError is called, and we are store the error in the state and setting the hasError to true.

When hasError is set to true we are displaying the message "Something went wrong:" and the Error Message.

If hasError is set to false then we are displaying the <WrappedComponent />

Then we are creating a DataViewerComponent and throwing an error in the DataViewerComponent if the data prop is empty.

When we will run the app you can see the message "Something went wrong: Please specify the data" message displayed.

Javascript Chat API and SDK | DeadSimpleChat

Easily add chat to your website or app within seconds. Pre-built chat with API and Javascript SDK | DeadSimpleChat.

DeadSimpleChat

Naming Conventions

We have seen many examples with Higher Order Components in this blog post. You might have noticed the HOCs being with the word with.

withErrorBoundary, withTranslation etc. There are no strict naming conventions with Higher Order Components but it is a good practice to prefix the HOC with the keyword with.

And the new component that we create after wrapping them with the HOC should contain the name of the HOC to indicate that this component has been enhanced with and HOC.

For e.g: We had created a HOC withTranslation and a component called as the LoginComponent and our enhanced componenet was called LoginComponentWithTranslation

const LoginComponentWithTranslation = withTranslation(LoginComponent, i18n);

Conclusion

In this blog post, we have learned what are Higher Order Components, how to create simple Higher Order Components.

We have also seen examples of scenarios where Higher Order Components are commonly used.

Hopefully, this post will give you a good understanding of how and where to use Higher Order Components in your application.

HOC is a good React pattern that promotes code reuse and helps you create clean and maintainable code.