Today I decided to get more familiar with Higher-Order Components (HOC) commonly used in libraries such as Redux (connect), react-i18next (translate) or react-router (withRouter). I have often used them as part of aforementioned libraries but never got the full grasp behind their magic. Today is the day to get underneath the carpet and see how they really work.

But before we begin, let's define what a Higher-Order Component is. According to React docs, Higher-Order Component is an advanced React pattern consisting of a function that takes a base (wrapper) component as an argument and returns a new, enhanced component. The best thing about it is that it allows to reuse component logic in a clean compositional way.

The abstract code for a HOC would look somewhat like this:

const higherOrderComponent = BaseComponent => {
    // ...
    // create new component from old one and update
    // ...
    
    return EnhancedComponent
}

In a more concrete form, a Higher-Order Component always takes a form similar to this:

import React from 'react';

const higherOrderComponent = BaseComponent => {
    class HOC extends React.Component {
        ...
        enhancements
        ...
  
        render() {
            return <BaseComponent newProp={newPropValue} {...this.props} />;
        }
    }
    
    return HOC;
};

Simple example

To get a better understanding, let's take a look at a simple example of a Higher-Order Component. Imagine we want to have several components that all get set random background value each time the component is rendered.

We do this by first defining a Higher-Order Component function called withColor. It accepts BaseComponent as a parameter and returns the enhanced component. EnhancedComponent that gets returned consists of a getRandomColor and render methods. Within the render method we return BaseComponent that gets assigned a new color prop and by taking advantage of {...this.props} desctructuring, BaseComponent also gets access to all the props which were passed to the BaseComponent from outside the HOC.

import React from 'react';

const withColor = BaseComponent => {
  class EnhancedComponent extends React.Component {
    getRandomColor() {
      var letters = '0123456789ABCDEF';
      var color = '#';

      for (let i = 0; i < 6; i++) {
        color += letters[Math.floor(Math.random() * 16)];
      }

      return color;
    }

    render() {
      return <BaseComponent color={this.getRandomColor()} {...this.props} />;
    }
  }

  return EnhancedComponent;
};

export default withColor;

To use the newly created HOC, we just have to export it with our base component passed as an argument.

import React from 'react';
import withColor from './withColor';

const ColoredComponent = props => {
  return <div style={{ background: props.color }}>{props.color}</div>;
};

export default withColor(ColoredComponent);

Finally, we render the new enhanced component the same way as we would render a regular component.

import React, { Component } from 'react';
import ColoredComponent from './ColoredComponent';

class App extends Component {
  render() {
    return (
      <div>
        <ColoredComponent someProp="Prop 1" />
        <ColoredComponent someProp="Prop 2" />
        <ColoredComponent someProp="Prop 3" />
      </div>
    );
  }
}

export default App;

Note that all the props defined at this level such as someProp will be passed further down the line via {...this.props} and new props such as color in the above example are defined explicitly from within the HOC.

Practical example

The previous example was a bit contrived. We could, for example, have used a utility function that generates random color, and then call it from within each component. The end result would have been the same with less code. Let's better look at an example where Higher-Order Component pattern is indeed the most elegant solution.

In the following example we will develop a Higher-Order Component that accepts BaseComponent and API URL to fetch the data that it needs. While the data is loading, it will show a loading state, and once the data is loaded we will display whatever BaseComponent renders based on the data. The end result will look as follows.

HOC example

To get started, we will first create a Higher-Order Component function. We will call it withLoader and initially set the data property of component's state to null. Once the component has mounted we will start fetching the data and when that's done, set the data property to the returned response.

As already mentioned, whilst the state is null we will show a loading state. And once we have the data fetched, we will return BaseComponent which in return will render markup based on the returned data. The Higher-Order Component function looks as follows:

import React from 'react';

const withLoader = (BaseComponent, apiUrl) => {
  class EnhancedComponent extends React.Component {
    state = {
      data: null,
    };

    componentDidMount() {
      fetch(apiUrl)
        .then(res => res.json())
        .then(data => {
          this.setState({ data });
        });
    }

    render() {
      if (!this.state.data) {
        return <div>Loading ...</div>;
      }

      return <BaseComponent data={this.state.data} {...this.props} />;
    }
  }

  return EnhancedComponent;
};

export default withLoader;

The components that use this HOC are fairly straightforward. We simply have to take the data from the props and use it as needed. In the example below we use the data to show a list of users.

As usual, we first need to import the Higher-Order Component function and pass the component as function argument. Along with it we also pass the API URL from which the data is fetched. See the code for a base component below:

import React from 'react';
import withLoader from './withLoader';

const Users = props => {
  return (
    <div>
      <h1>Users:</h1>
      <ul>{props.data.map(user => <li key={user.id}>{user.name}</li>)}</ul>
    </div>
  );
};

export default withLoader(Users, 'https://jsonplaceholder.typicode.com/users');

Similarly, we use another component to show a list of posts. The code is pretty much the same except for the component's name and API URL.

import React from 'react';
import withLoader from './withLoader';

const Posts = props => {
  return (
    <div>
      <h1>Posts:</h1>
      <ul>{props.data.map(post => <li key={post.title}>{post.title}</li>)}</ul>
    </div>
  );
};

export default withLoader(Posts, 'https://jsonplaceholder.typicode.com/posts/');

Now all that's left is to render the components. In order to do that we don't have to do anything special. Just import the components and render them where needed as usual. That's all πŸ˜ƒ

import React, { Component } from 'react';
import Users from './Users';
import Posts from './Posts';

class App extends Component {
  render() {
    return (
      <div>
        <Users />
        <Posts />
      </div>
    );
  }
}

export default App;

HOC caveats πŸ€”

  • HOC should always be a pure function. That means that the same enhanced component should always be returned with the same base component passed as a parameter.
  • Don’t use HOCs inside the render method. This makes React's reconciliation algorithm think that a new component is redeclared within each render, causing the whole subtree to be unmounted rather than just checked for differences.
  • Static methods do not get copied implicitly. This needs to be done explicitl. A good way to do it is with hoist-non-react-statics package.
  • Refs don’t get passed through.

Summary πŸ”₯πŸ”₯πŸ”₯

A Higher-Order Component has access to all the default React API, including state and the lifecycle methods. This allows to reuse logic in a very conscise way and make your code more elegant. As we have seen, it can be used for a variety of use cases but sometimes it's not the most conscise solution. Sometimes a simple utility function or a smart parent component is all that's needed. So just use Higher-Order Components at your best judgement and don't over-engineer things where not needed 😊