Higher-order Components

Higher-order Components

A higher-order function is a function that takes one or multiple functions as its arguments and returns a new function.

In React, higher-order functions are used for reusing component logic. This pattern is commonly referred to as Higher-order Components (HOC).

HOCs are common in third-party React libraries, such as Redux’s connect and Relay’s createContainer.

Resources

Official Documentation https://reactjs.org/docs/higher-order-components.html

Video Lecture https://courses.reacttraining.com/courses/advanced-react/lectures/3065962

How it works

Concretely, a higher-order component is a function that takes a component as its argument and returns a new, enhanced component.

const EnhancedComponent = higherOrderComponent(WrappedComponent);

To help you wrap your head around the basic structure of a HOC, let’s look at a barebones implementation of a Higher-order Component:

// This function takes a component...
function higherOrderComponent(WrappedComponent) {
  // ...and returns a new component...
  return class extends React.Component {
    render() {
      // That renders our original component, enhanced with additional data
      return <WrappedComponent data={['A', 'B', 'C']} {...this.props} />;
    }
  }
}

Note that a HOC doesn’t modify the input component, nor does it use inheritance to copy its behaviour. Rather, a HOC composes the original component by wrapping it in a container component. A HOC is a pure function with zero side-effects.

Now that you’ve had a chance to get accustomed with the basic syntax for writing a HOC, let’s take a look at a real-world example:

// This function takes a component...
function withSubscription(WrappedComponent, DataSource) {
  // ...and returns another component...
  return class extends React.Component {
    state = {
      data: DataSource.getData(),
    };

    componentDidMount() {
      // ... that takes care of the subscription...
      DataSource.addChangeListener(this.handleChange);
    }

    componentWillUnmount() {
      DataSource.removeChangeListener(this.handleChange);
    }

    handleChange = () => {
      this.setState({
        data: DataSource.getData(),
      });
    }

    render() {
      // ... and renders the wrapped component with the fresh data!
      return <WrappedComponent data={this.state.data} {...this.props} />;
    }
  };
}

// We can then use this HOC to subscribe our components to various data sources:
const ProductListWithSubscription = withSubscription(ProductList, ProductStore);
const TagListWithSubscription = withSubscription(TagList, TagStore);

And that’s it! The wrapped component receives all the props of the container, along with a new prop, data, which it uses to render its output. The HOC isn’t concerned with how or why the data is used, and the wrapped component isn’t concerned with where the data came from.

Because withSubscription is a normal function, you can add as many or as few arguments as you like. In this case, we decided to pass the data source as an argument to make our HOC even more re-usable.

When to use this pattern

As a rule of thumb, Higher-order Components should be used sparingly.

We want the bulk of our components to be stateless and solely focused on presentational concerns. For cross-cutting concerns, for example, when you need to provide state or functionality related to the top level application, consider pulling these out into a higher order component (HOC).

For a real-world example of how Higher-order Components are used in Shopify, take a look at the [withForm](https://github.com/Shopify/web/blob/master/app/utilities/form/with-form.tsx) HOC.

When not to use this pattern

In most situations, you probably don’t need a Higher-order Component. Writing HOCs can be quite verbose and involves a lot of conventions and ceremony surrounding passing props vs consuming HOC props and hoisting statics.

They create indirection and are typically harder to reason about then the Children as function pattern, which we’ll look at in the next chapter.

For further reading on why HOCs might not be necessary for your use-case:

https://medium.com/tandemly/im-breaking-up-with-higher-order-components-44b0df2db052 https://cdb.reacttraining.com/use-a-render-prop-50de598f11ce https://youtu.be/BcVAq3YFiuc

Common mistakes

Don’t Use HOCs Inside the render Method React’s diffing algorithm (called reconciliation) uses component identity to determine whether it should update the existing subtree or throw it away and mount a new one. If the component returned from render is identical (===) to the component from the previous render, React recursively updates the subtree by diffing it with the new one. If they’re not equal, the previous subtree is unmounted completely.

Normally, you shouldn’t need to think about this. But it matters for HOCs because it means you can’t apply a HOC to a component within the render method of a component:

class MyComponent extends React.Component {
  render() {
    // A new version of EnhancedComponent is created on every render
    // EnhancedComponent1 !== EnhancedComponent2
    const EnhancedComponent = enhance(MyComponent);

    // That causes the entire subtree to unmount/remount each time!
    return <EnhancedComponent />;
  }
}

The problem here isn’t just about performance — remounting a component causes the state of that component and all of its children to be lost.

Instead, apply HOCs outside the component definition so that the resulting component is created only once. Then, its identity will be consistent across renders. This is usually what you want, anyway.

// Create the HOC outside of the component's definition
const EnhancedComponent = enhance(MyComponent);

class MyComponent extends React.Component {
  render() {
    return <EnhancedComponent />;
  }
}

In the rare cases where you need to apply a HOC dynamically, you can also do it inside a component’s lifecycle methods or its constructor.

Further reading

https://reactjs.org/docs/higher-order-components.html

Last updated