ReactJS Higher-Order Components in Plain English

There are many ways to write efficient and reusable modules in ReactJS to keep the codebase maintainable. By using Higher-Order Components(HOCs) you can achieve that. In today's tutorial, we are going to cover what HOCs are, the purpose they serve, various use cases and considerations to keep in mind while making use of HOCs in React.

Why Do We Use HOCs In the First Place?

Before we try to unravel the mystery of what HOCs are, let us first understand why we need them in the first place.

While creating a component that does many tasks, e.g. making API calls to the server, processing that data and showing the data in the webpage in some complex form, it is a good idea to split the component into a presentational and container component.

Technically speaking, presentational components are the ones which are concerned with how things on your website look. For example, the header, sidebar, user information, media content section of our website can be viewed as presentational components.

Container components are the ones that are concerned with doing all the heavy lifting behind the curtains like implementing all the core logic, making API requests, process data from API request and provide data to be shown by presentational components.

Simply put, presentational components are the actors who perform on stage in front of a live audience while container components are the behind the scenes crew who take care of audio, lighting, make-up or even training the actors to run the show successfully.

Keeping our UI and core logic in two different sections makes web developers' lives easier. If a designer makes any changes to UI, then we can simply update the presentational components, while if some change is required from the product or the backend side the container components can be tweaked accordingly.

We can also reuse the presentational and container components elsewhere if required. Higher-order components are a special type of functions which allow us to reuse the same container components and wrap them over multiple presentational components.

HOCs can be seen as the Marvel Studio that can sign particular actors, writers and crew to release the Avengers movie and then reuse same actors, writers, and crew to release individual superhero movies like Captain America or Ironman.

In our example, Avengers and other movies are presentational components that are displayed on the screen and have an impact on end users.

audience-1868137_640.jpg

In this case, actors, writers, and the crew are the container components brought together by Marvel Studios via HOCs.

HOCs do the heavy work of binding together the container components to presentational components giving enough flexibility to make changes to core logic without affecting the UI layer.

personal-260813_640.jpg

Where Does the Name Higher-Order Component Come From?

HOCs draw inspiration from higher-order functions in JavaScript. They are used to decompose complex modules into a layer of simple ones with each one having a specific unique task to take care of.

Higher-order functions are functions that operate on other functions, either by taking them as arguments or by returning them.

Example of a higher-order function:

function makeMultiplier(multiplier) {
   return function(x) { return multiplier * x }
}

const double = makeMultiplier(2);
console.log(double(5) ); // 10
console.log(double(12) ); // 24

In the example above, the function makeMultiplier takes an input parameter as a multiplier and returns a function as output. Whenever we call makeMultiplier function it actually creates and gives us back another function we can make a call to again. This is going to sound a bit strange at first and will take a moment to make sense of but we are essentially creating a new function from a function. Here makeMultiplier is a parent function, which gives us back a nested function as its child when we assign makeMultiplier to double variable above. Now, whenever we call the double function we get the value multiplied by number 2 back as a result.

Similarly, HOCs in ReactJS create and return a class when they are initialized by taking a child class as an input parameter.

Functional Programming in ReactJS

If you have used stateless components in any of your projects you might be familiar with the following syntax:

import React from ‘react’;

const GreetUser = (name) => (
 <div>{`Hello ${name}`}</div>
);

export default GreetUser;

In the above snippet, the GreetUser variable is a component which takes name value as a prop and returns a ReactJS element(UI) in return. Also, we notice that GreetUser can be actually viewed as a JavaScript function. Stateless components are essentially functions which return React elements where each such function is responsible for achieving a single task. Our entire website is a collection of functions used for modularising complex tasks into simple and manageable ones.

Our stateless component is a pure function since it returns the same UI given the same props as input. This is how the basic principles of functional programming are introduced in ReactJS ecosystem.

Implementing HOCs in ReactJS

Now that we have understood why we use higher-order components, let's see what they are and how to implement them.

Here's our next code snippet...

import React from 'react';

const sourceData = {
   'movie': 'Captain America: The First Avenger',
   'actor': 'Chris Evans',
   'productionCompany': 'Marvel Studios'
}

const dataSourceHOC = MovieComponent => class extends React.Component {
   componentWillMount() {
       this.setState({
         data: sourceData
      });
   }
   render() {
      return <MovieComponent {...this.props} {...this.state}     />;
   }
};

class Movie extends React.Component {
   render() {
       return (
         <div>
            <p>Movie: {this.props.data.movie}</p>
            <p>Starring: {this.props.data.actor}</p>
            <p>Production Company: {this.props.data.productionCompany}</p>
         </div>
      )
   }
}

export default dataSourceHOC(Movie);

In the example above, our objective is to show movie details from a sample JSON object. Here we first define a static sourceData object similar to one a component receives from reducer after making API calls to a server.

dataSourceHOC is a function which takes MovieComponent as an input parameter. In the componentWillMount method, we assign sourceData object to a state variable and pass the state object as a prop to MovieComponent in the render method of the HOC. The MovieComponent receives the data for rendering as a prop value through which we can show the data to the client. Here you should note that dataSourceHOC is a function which returns a class. The returned class contains a child class(MovieComponent) which was passed as an input parameter to the function. Hope this example gives you a basic idea of how HOCs are implemented in ReactJS.

HOC can take a presentational component as input, add that presentational component as a child of a common parent container component with additional properties and returns the parent component back as the output of the function which is why it can also be referred to as enhancer component.

Let's say now we add an actual API call in the dataSourceHOC component above...

import React from 'react';

const dataSourceHOC = MovieComponent => class extends React.Component {
   state = {
   'data': {}
   }
   componentDidMount() {
       // make actual API call using fetch keyword
       fetch('Some url to fetch movie details')
       .then((resp) => resp.json())
       .then(function(responseObject) {
        this.setState({'data': responseObject})
       }
   }
   render() {
      return <MovieComponent {...this.props} {...this.state} />;
   }
};

class Movie extends React.Component {
   render() {
       return (
         <div>
            <p>Movie: {this.props.data.movie}</p>
            <p>Starring: {this.props.data.actor}</p>
            <p>Production Company: {this.props.data.productionCompany}</p>
         </div>
      )
   }
}

export default dataSourceHOC(Movie);

As you can notice we have made changes to the container component only, while presentation component is completely unaware of the fact that data source has changed internally. Using HOC as an abstraction or middle layer gives us the freedom to make any kind of changes in the core logic of container components without affecting the UI layer.

A well-known example of HOCs is in the Redux framework which is generally used for state management in complex ReactJS projects. If you have ever used Redux in any of your projects you might have used connect() method to pass reducer state as props and actions to individual components while exporting a class. That connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options]) method is actually used to convert a normal ReactJS base component into higher-order component which means if you have ever used Redux in any of your projects then you have been using HOCs without even knowing about them.

Here's a quite old GitHub gist which shows the implementation of HOCs can be found.

Adding a common loader to child components until the API call for fetching data required by the component is finished

Adding a loader until we are ready with the data to be shown in the component is a task which needs to be done quite a lot while working on a single website. Instead of creating multiple instances of such loader components we can simply write a HOC to show a loader and use it throughout our website.

Example:

import React from 'react';

const commonLoadingComponent = (ComposedComponent) => {
    return class extends React.Component {
       render() {
          if(this.props.isfetchingContentData){
            return(
              <div>
              <span>Loading your content...</span>
              <div>Custom loader or spinner</div>
              </div>
            )
          } else {
            return <ComposedComponent {...this.props}/>;
          }
       }
    };
}

class ChildComponent extends React.Component {
   render() {
       return (
         <div>
            Child Component
         </div>
      )
   }
}

export default commonLoadingComponent(ChildComponent);

In the above example, the HOC component introduces a new prop called isFetchingContentData through which we can conditionally change the visibility of the passed component. A simple use case could be to render loader when we make API request to backend server by setting the value of isFetchingContentData to true and when we have received a response from the API request we can set it to false.

Adding a fallback error component in case any unexpected scenario occurs in our application

We can add a common error component in case there is an error or no response from backend API request.

import React from 'react';

const commonErrorComponent = (ComposedComponent) => {
    return class extends React.Component {
       render() {
          if(this.props.isfetchingContentDataError){
            return(
              <div>
              <span>Some error ocurred</span>
              <div>Add code for retrying api request</div>
              </div>
            )
          } else {
            return <ComposedComponent {...this.props}/>;
          }
       }
    };
}

This is a conditional render which can be reused in most the parts of the website where we make API calls to the server.

Conditionally rendering components

HOCs can be used to conditionally render components based on passed prop values. This can come in handy especially when deploying a particular feature for a limited set of users or when we want to toggle certain elements visibility based on user interactions.

import React from 'react';

const commonConditionalComponent = (ComponentOnPass, ComponentOnfail) => {
    return class extends React.Component {
       render() {
          return(
            this.props.test ? 
            <ComponentOnPass {...props} />
            :
            <ComponentOnfail {...props} />
          )
       }
    };
}

Iterate over input JSON array and output a wrapped component like content/user card

If we need to show a list of elements or tables multiple times in our website then HOC pattern can help in overcoming code duplication at multiple places.

import React from 'react';

const showListItems = ListItemComponent => {
  return class extends React.Component {
    render() {
      return(
        <div className={type}>
          {this.props.data.map(item => (
            <ListItemComponent {...item} key={item.id} />
          ))}
        </div>
      )
    }
  }
}

What You Need to Know While Using Higher-Order Functions

Refs are not passed through

A ref is not really a prop — like a key, it’s handled specially by React which is why we pass through all props to the wrapped component, refs are not passed forward. If you add a ref to an element whose component is the result of a HOC, the ref will point to an instance of the outermost container component, not the wrapped component.

HOCs should not be used inside render function

If HOC is inside the render method of a component then remounting a component causes the state of that component and all of its children to be lost effectively creating a new version of HOC in every render call.

Instead, apply HOCs outside the component definition so that the resulting component is created only once. Then, its identity will be consistent across renders and there will not be any re-renders of HOC.

Conclusion

Using HOC can greatly simplify the way we handle core logic which drives our components. It makes our code maintainable and helps us in keeping UI layer separate from logic layer allowing us to refactor any of those parts with ease without affecting the overall functionality drastically.

Hope you found the concept of HOCs useful for implementing in your upcoming ReactJS projects. Let me know how you liked this tutorial and feel free to share your HOC examples in the comment section. You can also follow me on Hashnode to stay up to date with all my comments and posts.

Saurabh Mhatre's photo

Saurabh Mhatre

Mumbai, Maharashtra, India

smhatre59
Write your comment…

Nice article!

Thanks for the feedback

Reply to this…