Refactoring Higher-Order Components (HOC) to React Hooks
Tags: react, javascript
Published: 2019-10-07 08:56:07

---
title: Refactoring Higher-Order Components (HOC) to React Hooks
published: true
description: Before the release of React Hooks you needed Higher-Order Components (HOC) to reuse class components, after you can use Hooks to use React features in function components
tags: react, javascript
cover_image: https://i.imgur.com/aPLrtsT.png
---



With the release of React version 16.8, also labelled "The One With Hooks", the long awaited Hooks pattern was introduced. This patterns let's you use state, lifecycles and (almost) any other React feature without the use of classes. If you've been working with React for a longer period, this either felt like a big relief or a giant shock. To me it felt as a relief, as I already preferred using function components over class components. To avoid having to deal with too many class components, some of the projects I'm working on are reusing class logic using Higher-Order Components (HOC) - which can get quite complex. In this post I'll be converting one of these HOCs to a custom Hook, to demonstrate the power of this "new" pattern.

*Sidenote: You can use classes or Hooks depending on your own preference as there are no breaking changes for the use of classes scheduled - yet. 
And when you're reading this article you probably already have tried any of the Hooks or at least have read a lot about it. In case you haven't, [this](https://reactjs.org/docs/hooks-overview.html) overview in the official React documentation is a great place to start*

## Higher-Order Components (HOC)
As briefly mentioned before a HOC is a pattern to reuse (class) component logic across your React application. That way you don't have to duplicate logic that's in example based on state updates, like data fetching or routing. The React docs describe a HOC as a "function that takes a component and returns a new component", roughly meaning the component that is used as input for the HOC will be enhanced and returned as a different component. HOCs are very commonly used in React by packages like `react-router` or `react-redux`. Examples of HOCs in these packages are the `withRouter` and `connect` HOCs. The first one lets you access routing props in any component that you pass to it, while the latter makes it possible to connect to the Redux state from the input component.

Creating a HOC isn't that hard and is very well explained in the [documentation](https://reactjs.org/docs/higher-order-components.html) on the official React website, which I'll demonstrate by creating a new HOC that's called `withDataFetching`. This will add basic data fetching features using state and lifecycles to any component that you pass to this HOC. Using the Github API a component will be created that renders a list of my public repositories.

- The starting point is create a function that takes a component as input and returns a different component based on this component. This function does nothing more than construct a new class component `WithDataFetching` that returns the input component `WrappedComponent`.


```js
import React from "react";

const withDataFetching = props => WrappedComponent => {
  class WithDataFetching extends React.Component {

    render() {
      return (
        <WrappedComponent />
      );
    }
  }

  return WithDataFetching;
};

export default withDataFetching;
```


- After which you can add the data fetching logic to this function, by using state and lifecycles. In the `constructor()` the initial state values are set, while the data fetching is done in in the asynchronous `componentDidMount()` lifecycle using the `fetch()` method.

```js
import React from "react";

const withDataFetching = props => WrappedComponent => {
  class WithDataFetching extends React.Component {
    constructor() {
      super();
      this.state = {
        results: [],
        loading: true,
        error: ""
      };
    }

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

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

    async componentDidMount() {
      this.fetchData();
    }

    // ...
  }

  return WithDataFetching;
};

export default withDataFetching;
```

- In the `render()` method the `WrappedComponent` is returned and the state values `loading`, `results` and `error` should be passed to it as props. That way the results returned by the data fetching will become available on the input component.

```js
import React from "react";

const withDataFetching = props => WrappedComponent => {
  class WithDataFetching extends React.Component {
    // ...

    render() {
      const { results, loading, error } = this.state;

      return (
        <WrappedComponent
          results={results}
          loading={loading}
          error={error}
          {...this.props}
        />
      );
    }
  }

  return WithDataFetching;
};

export default withDataFetching;
```

- And finally you can set the display name of the component that's being returned by the HOC, as otherwise this new component is hard to track in example the React DevTools. This can be done by setting the `displayName` of the `WithDataFetching` component.

```js
import React from "react";

const withDataFetching = props => WrappedComponent => {
  class WithDataFetching extends React.Component {
    // ...

    render() {
      // ...
    }
  }

  WithDataFetching.displayName = `WithDataFetching(${WrappedComponent.name})`;

  return WithDataFetching;
};

export default withDataFetching;
```

This created the HOC that can be used to add data fetching features to any component that is passed to this function. As you can see this HOC is setup as a [curried function](https://medium.com/javascript-scene/curry-and-function-composition-2c208d774983), meaning it will take several arguments. Therefore you cannot only pass a component as a parameter, but also other values as a second parameter. In the case of the `withDataFetching` HOC you can also send an object containing props for the component, where the prop `dataSource` is used as the url for the `fetch()` method. Any other props that you'll pass in this object will be spread on the `WrappedComponent` that is returned.

- In a function component that's called `Repositories` the `withDataFetching` HOC component must be imported. The default export of this file is the HOC component that takes the `Repositories` component and an object containing the field `dataSource`. The value of this field is the url to the Github API to retrieve the repositories for a username.

```js
import React from "react";
import withDataFetching from "./withDataFetching";

function Repositories() {
  
  return '';
}

export default withDataFetching({
  dataSource: "https://api.github.com/users/royderks/repos"
})(Repositories);
```

- As the HOC adds data fetching capabilities to the `Repositories` component, the props `loading`, `results` and `error` are passed to this component. These result from the state and lifecycle values in `withDataFetching`, and can be used to display a list of all the repositories. When the request to the Github API has not resolved yet or an error occurs, a message will be displayed instead of the repositories list.

```js
import React from "react";
import withDataFetching from "./withDataFetching";

function Repositories({ loading, results, error }) {
  if (loading || error) {
    return loading ? "Loading..." : error.message;
  }

  return (
    <ul>
      {results.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/royderks/repos"
})(Repositories);
```

With this last change the `Repositories` is able to display the results from the data fetching that's done in the HOC. This can be used for any endpoint or component, as HOCs make reusing logic easy. 

In this [CodeSandbox](https://codesandbox.io/s/higher-order-components-and-react-hooks-wby4w) below you can see the results of passing the `Repositories` component to the HOC:
{% codesandbox higher-order-components-and-react-hooks-wby4w module=/src/Repositories.jsx %}

## Custom Hooks

In the introduction of this post I told that Hooks make it possible to use React features, like state, outside class components. To correct myself: Hooks can only be used in function components. Also, by building custom Hooks you can reuse the data fetching logic from the previous HOC in almost the same matter. But first let's have a brief look into Hooks, and specifically the `useState()` and `useEffect()` Hook.

- The [`useState()` Hook](https://reactjs.org/docs/hooks-state.html) let's you handle state from any function component, without having to use a `constructor()` and/or `this.setState()` method.

- The [`useEffect()` Hook](https://reactjs.org/docs/hooks-effect.html) is the equivalent of both the `componentDidMount()` and `componentDidUpdate()` lifecycle method. Using just this Hook you can watch for updates of specific (state) variables or no variables at all. 

If you aren't familiar with these Hooks yet this might sound confusing, but lucky for you you'll use both Hooks to create a custom `useDataFetching()` Hook. This Hook will have the same data fetching logic as the `withDataFetching` HOC, and call the Github API using the `fetch()` method. The Hook will return the same values as the HOC, which are `loading`, `results` and `error`. 

- First you need to create the function `useDataFetching` that takes the parameter `dataSource`, this parameter is the url that needs to be fetched later on. This custom Hook needs `react` as a dependency as you want to use React features, from where you import the two Hooks you'll be using.

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

function useDataFetching(dataSource) {

  return {};
}

export default useDataFetching;
```

- The Hook should return the values `loading`, `results` and `error`; these values must be added to the state of this Hook and returned afterwards. Using the `useState()` Hook you can create these state values, and also a function to update these values. But first create the state values and return them at the end of this `useDataFetching` function.

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

function useDataFetching(dataSource) {
  const [loading, setLoading] = useState(true);
  const [results, setResults] = useState([]);
  const [error, setError] = useState("");

  return {
    loading,
    results,
    error
  };
}

export default useDataFetching;
```

The initial values for the return values are set when calling the `useState` Hook, and can be updated using the second value of array that's being returned by the Hook. The first value is the current state value and should therefore be returned at the end of the custom Hook.

- In the `withDataFetching` HOC there was a function to send a request to the Github API called `fetchData`. This function must also be added to the custom Hook. The only difference is that the state values aren't updated using the `this.setState()` method, but by calling the update functions returned by the `useState()` Hooks. This `fetchData` function must be put inside the `useEffect()` Hook, which allows you to control when this function is being called.

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

function useDataFetching(dataSource) {
  const [loading, setLoading] = useState(true);
  const [results, setResults] = useState([]);
  const [error, setError] = useState("");

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

        if (json) {
          setLoading(false);
          setResults(json);
        }
      } catch (error) {
        setLoading(false);
        setError(error.message);
      }

      setLoading(false);
    }

    fetchData();
  }, [dataSource]);

  return {
    error,
    loading,
    results
  };
}

export default useDataFetching;
```

In the code block above the `fetchData` function is called when the value for `dataSource` is updated, as this value is added to the dependency array for the `useEffect()` Hook. 

From a function component you're now able to call the custom `useDataFetching()` Hook to use the data fetching values in that component. Different then for the HOC these values aren't added as props to the component, but returned by the Hook.

- In a new function component called `RepositoriesHooks` you need to import `useDataFetching()` and destructure the values for `loading`, `results` and `error` from the result returned from this Hook. The url to retrieve all the repositories of a user from the Github API should be added as a parameter.

``` js
import React from "react";
import useDataFetching from "./useDataFetching";

function RepositoriesHooks() {
  const { loading, results, error } = useDataFetching("https://api.github.com/users/royderks/repos");

  return '';
}

export default RepositoriesHooks;
```

- To display the repositories in a list you can copy the return values from the `Repositories` components, as nothing has changed except for the manner in which the values for `loading`, `results` and `error` are added in this component.

``` js
import React from "react";
import useDataFetching from "./useDataFetching";

function RepositoriesHooks() {
  const { loading, results, error } = useDataFetching(
    "https://api.github.com/users/royderks/repos"
  );

  if (loading || error) {
    return loading ? "Loading..." : error.message;
  }

  return (
    <ul>
      {results.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 RepositoriesHooks;
```

By creating the custom `useDataFetching` Hook you're now able to use data fetching in any function component using React Hooks instead of by creating a HOC. If you want to see the changes affected in the [CodeSandbox](https://codesandbox.io/s/higher-order-components-and-react-hooks-wby4w) you need to comment out the import of the `Repositories` component in `src/index.js` and import the `RepositoriesHooks` component instead.

``` js
import React from "react";
import ReactDOM from "react-dom";

// import Repositories from "./Repositories";
import { default as Repositories } from "./RepositoriesHooks";

function App() {
  return (
    <div className="App">
      <h1>My Github repos</h1>
      <Repositories />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
```

## Summary

The new Hooks pattern makes it possible to use state, lifecycles and other features from React outside of class components. Earlier, you were only able to use these features in class components and therefore needed Higher-Order Components (HOC) to reuse any of this logic you put in there. From React version 16.8 onwards you're able to use Hook to access React features like state from function components. By creating custom Hooks, such as the `useDataFetching()` Hook above, you can reuse in example state logic from any function component.

Hopefully this post helps you deciding whether or not you should convert any of your HOCs to a custom Hook! Don't forget to leave any feedback, or follow me on [Twitter](https://twitter.com/gethackteam) to stay up-to-date 😄!