Higher Order Component Tutorial
Higher Order Component Tutorial

Today you will learn what are higher order components in React, do we still need them if we have React hooks and how to refactor higher order components in React hooks code.

Classes and high order components is an older approach to write React applications but you will still find a lot of React projects which still use classes or high order components. This is why you still need to know how to use them.

So here I already prepared a React component for us.

class Repos extends Component {
    constructor() {
      super();
      this.state = {
        isLoading: true,
        data: [],
        error: null,
      };
    }

    componentDidMount() {
      this.fetchData();
    }

    async fetchData() {
      try {
        const data = await fetch(props.dataSource);
        const json = await data.json();

        if (json) {
          this.setState({
            data: json,
            isLoading: false,
          });
        }
      } catch (error) {
        this.setState({
          isLoading: false,
          error: error.message,
        });
      }
    }

    render() {
      const { data, isLoading, error } = this.state;

      return (
        <ul>
          {this.state.data.map(({ id, html_url, full_name }) => (
            <li key={id}>
              <a href={html_url} target="_blank" rel="noopener noreferrer">
                {full_name}
              </a>
            </li>
          ))}
        </ul>
      );
    }
}

So here we have a classic React component where we fetch some data on initialize, we show loading and error states and after getting data we render them on the screen.

Project results

Higher order component

Now the question is what does this code has to do with higher order components and what is it at all? Higher order component is a function which will return a function which will return a component. And it's difficult to understand this so we need to build an example.

As you can see Repos class is a typical example of fetching data. We have isLoading, data and error properties. Exactly the same approach we do again and again in every component where we want to fetch something. This is why it makes sense to move this logic outside of our component and make it reusable. Higher order component is a best approach in this case. This is why I created a file withDataFetching.js. We prefix all our higher order components with with and this is a code style which is typical for React.

// withDataFetching.js
const withDataFetching = props => WrapperComponent => {
  class WithDataFetching extends Component {
  }

  return WithDataFetching
}

So here we created a function which gets props argument as a parameter and return a function which has a WrapperComponent as a parameter. Inside we create a React class component and return it. And the typical usage will look like this.

withDataFetching({url: ''})(Repos)

So we call withDataFetching and pass url inside props. This is out configuration of higher order component. After this we call our returned function on the component that we want to wrap. In our case it's Repos component. Now let's move whole fetching logic to withDataFetching.

// withDataFetching.js

import { Component } from "react";

const withDataFetching = (props) => (WrapperComponent) => {
  class WithDataFetching extends Component {
    constructor() {
      super();
      this.state = {
        isLoading: true,
        data: [],
        error: null,
      };
    }

    componentDidMount() {
      this.fetchData();
    }

    async fetchData() {
      try {
        const data = await fetch(props.dataSource);
        const json = await data.json();

        if (json) {
          this.setState({
            data: json,
            isLoading: false,
          });
        }
      } catch (error) {
        this.setState({
          isLoading: false,
          error: error.message,
        });
      }
    }
    render() {
      const { data, isLoading, error } = this.state;
      return (
        <WrapperComponent
          data={data}
          isLoading={isLoading}
          error={error}
          {...props}
        />
      );
    }
  }

  return WithDataFetching;
};

export default withDataFetching;

As you can see it is mostly copy paste from Repos component with a twist inside render. What we want to do in render is to return our WrapperComponent which we got as an argument and provide inside all props that we got and additionally properties from withDataFetching like data, isLoading, error. So the goal of higher order component is to move logic outside of our component and pass needed values back inside our component as props.

Now let's clean our Repos and use withDataFetching on it.

// Repos.js

import withDataFetching from "./withDataFetching";

const Repos = ({ isLoading, error, data }) => {
  if (isLoading) {
    return "Loading...";
  }

  if (error) {
    return error.message;
  }

  return (
    <ul>
      {data.map(({ id, html_url, full_name }) => (
        <li key={id}>
          <a href={html_url} target="_blank" rel="noopener noreferrer">
            {full_name}
          </a>
        </li>
      ))}
    </ul>
  );
};

export default withDataFetching({
  dataSource: "https://api.github.com/users/monsterlessonsacademy/repos",
})(Repos);

As you can see our Repos component now just get 3 additional props from higher order component. Also as an export we return now withDataFetching call which takes a url to fetch and our Repos component. In browser everything is working exactly like before but we moved our fetching logic to higher order component.

Higher order component moves logic outside and makes it reusable between components.

React hooks approach

But this is not the modern way of doing things. Inside React we have now React hooks this is why we don't use higher order components in new projects anymore. And the question is actually "Why?". As you saw with higher order component we have quite strange syntax with returning a function of the function and really often we need to combine several higher order component together.

export default withHistory(withRouter(withDataFetching({
  dataSource: "https://api.github.com/users/monsterlessonsacademy/repos",
})))(Repos);

This makes our code more complex and less maintainable.

The modern approach to replace higher order components is by creating custom React hooks. So let's move our logic from withDataFetching to useDataFetching as we prefix all our custom hooks with use.

// useDataFetching.js

import { useState, useEffect } from "react";

export default (dataSource) => {
  const [isLoading, setIsLoading] = useState(true);
  const [data, setData] = useState([]);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const data = await fetch(dataSource);
        const json = await data.json();

        if (json) {
          setIsLoading(false);
          setData(json);
        }
      } catch (error) {
        setIsLoading(false);
        setError(error.message);
      }
    };

    fetchData();
  }, [dataSource]);

  return { isLoading, data, error };
};

First of all we created a custom hook with just a single argument dataSource which is a url to fetch data from. Inside we have 3 useState calls to store data, isLoading and error properties which we return at the end. We put all our fetching logic inside useEffect because fetching data is a side effect and we trigger it when our dataSource changes. On success and error we simply set data in our state.

Now let's refactor our component to work with our custom hook.

// Repos.js

import useDataFetching from "./useDataFetching";

const ReposHooks = () => {
  const { error, isLoading, data } = useDataFetching(
    "https://api.github.com/users/monsterlessonsacademy/repos"
  );

  if (isLoading) {
    return "Loading...";
  }

  if (error) {
    return error.message;
  }

  return (
    <ul>
      {data.map(({ id, html_url, full_name }) => (
        <li key={id}>
          <a href={html_url} target="_blank" rel="noopener noreferrer">
            {full_name}
          </a>
        </li>
      ))}
    </ul>
  );
};

export default ReposHooks;

So at the beginning we just call useDataFetching and pass a url inside. We get back our error, data, isLoading which are just local properties that we can use. As you can see the code is much easier to understand and support.

On the real example you saw the difference between higher order components and custom hooks. Also if you are interested in 5 most popular React interview questions don't forget to check this post.

📚 Source code of what we've done