What are Higher-Order Components in React?

Avatar of Kingsley Silas
Kingsley Silas on

📣 Freelancers, Developers, and Part-Time Agency Owners: Kickstart Your Own Digital Agency with UACADEMY Launch by UGURUS 📣

If you have been in the React ecosystem for a while, there is a possibility that you have heard about Higher Order Components. Let’s look at a simple implementation while also trying to explain the core idea. From here you should get a good idea of how they work and even put them to use.

Why Higher-Order Components?

As you build React applications, you will run into situations where you want to share the same functionality across multiple components.

For example: you need to manage the state of currently logged in users in your application. Instead of managing that state across all of the components that need that state, you could create a higher-order component to separate the logged in user state into a container component, then pass that state to the components that will make use of it.

The components that receive state from the higher-order component will function as presentational components. State gets passed to them and they conditionally render UI based on it. They do not bother with the management of state.

Let’s see another example. Say you have three JSON files in your application. These files contain different data that will be loaded in your application in three different components. You want to give your users the ability to search the data loaded from these files. You could implement a search feature in all three of the components. This duplication may not be an issue at first, but as your application grows and more components need this functionality, the constant duplication will be cumbersome and prone to problems.

A better way forward is to create a higher-order component to handle the search functionality. With it, you can wrap the other components individually in your higher-order component.

How do Higher-Order Components Work?

The React docs say that higher-order components take a component and return a component.

The use of higher-order components comes in handy when you are architecturally ready for separating container components from presentation components. The presentation component is often a stateless functional component that takes props and renders UI. A stateless functional components are plain JavaScript functions that do not have states. Here’s an example:

import React from 'react'

const App = ({name}) => {
  return (
    <div>
      <h2>This is a functional component. Your name is {name}.</h2>
    </div>
  )
}

ReactDOM.render(<App name='Kingsley' />, document.getElementById("root"));

The container component does the job of managing of state. The container, in this case, is the higher-order component.

In the search example we talked about earlier, the search component would be the container component that manages the search state and wraps the presentation components that need the search functionality. The presentation components otherwise have no idea of state or how it is being managed.

A Higher-Order Component Example

Let’s start with a basic example. Here’s a higher-order component that transforms and returns usernames in uppercase:

const hoc = (WrappedComponent) => (props) => {
  return (
    <div>
      <WrappedComponent {...props}>
        {props.children.toUpperCase()}
      </WrappedComponent>
    </div>
  )
}

This higher-order component receives a WrappedComponent as an argument. Then it returns new component with props passed to it creating a React element. We call .toUpperCase() on the props.children, to transform the passed props.children to uppercase.

To make use of this higher-order component, we need to create a component that receives props and renders the children.

const Username = (props) => (
  <div>{props.children}</div>
)

Next, we wrap Username with the higher-order component. Let’s store that in a variable:

const UpperCaseUsername = hoc(Username)

In our App component, we can now make use of it like this:

const App = () => (
  <div>
    <UpperCaseUsername>Kingsley</UpperCaseUsername>
  </div>
);

The UpperCaseUsername component is merely a rendering of the Username UI that, in turn, pulls in state from the WrappedComponent acting as the higher-order component.

A More Practical Higher-Order Component

Imagine we want to create a list of locations with a search form that filters them. The JSON will be in flat files and loaded as separate components. Let’s start by loading the data.

Our first component will load locations for our users. We will make use of .map() to loop through the data contained in that JSON file.

import React from 'react'
// Where the data is located
import preload from './locations.json'
// Manages the data
import LocationCard from './LocationCard'

// Renders the presentation of the data
const Location = (props) => {
  return (
    <div>
      <div>
        <div>
          <h2>Preferred Locations</h2>
        </div>
      </div>
      <div>
        {preload.data
          .map(location => <LocationCard key={location.id} {...location} />)}
      </div>
    </div>
  )
}
export default Location

This component will render the data in a LocationCard component. I moved that to a different component to keep things clear. This component is a functional component that handles the presentation of our data. The data (location) from the file is received via props, and each location will be passed down to the LocationCard component.

Now we need a second component that, eventually, also will need search functionality. It will be very similar to the first component we just built, but it will have a different name and load data from a different place.

We want our users to be able to search for items using an input field. The list of items displayed on the app should be determined by the state of the search. This functionality will be shared across the two components we are working on. Thanks to the idea of higher order components, we can create a search container component and wrap it around other components.

Let’s call the component withSearch. This component will render the input field for our search and also manage our searchTerm state. The searchTerm will be passed as props to the wrapped component, which will be used to filter the pulled data:

import React, { Component } from 'react'

const withSearch = (WrappedComponent) => {
  return class extends Component {
    state = {
      searchTerm: ''
    }
    handleSearch = event => {
      this.setState({ searchTerm: event.target.value })
    }
 
    render() {
      return (
        <div>
          <div>
            <input onChange={this.handleSearch} value={this.state.searchTerm} type="text" placeholder="Search" />
          </div>
          <WrappedComponent searchTerm={this.state.searchTerm} />
        </div>
      )
    }
  }
 
}
export default withSearch

The searchTerm is given a state of an empty string. The value entered by the user in the search box is obtained and used to set the new state for searchTerm. Next, we pass searchTerm to the WrappedComponent. We will make use of this when filtering the data.

To make use of the higher-order component, we need to make some changes to our presentational component.

import React, { Component } from 'react'
// Where the data is located
import preload from './locations.json'
// Searches the data
import withSearch from './withSearch
// Manages the data
import LocationCard from './LocationCard'

// Renders the presentation of the data
const Location = (props) => {
  const { searchTerm } = props
  
  return (
    <div>
      <div>
        <div>
          <h2>Preferred Locations</h2>
        </div>
      </div>
      <div>
        {preload.data
          // Filter locations by the inputted search term
          .filter(location => `${location.name} ${location.zone} ${location.region}`.toUpperCase().indexOf(searchTerm.toUpperCase()) >= 0)
          // Loop through the locations
          .map(location => <LocationCard key={location.id} {...location} />)}
      </div>
    </div>
  )
}
export default withSearch(Location)

The first thing we did above is to import the higher-order component. Then we add a filter method to filter the data based on what the user enters in the search input. Last, we need to wrap it with the withSearch component.

See the Pen hoc Pen by Kingsley Silas Chijioke (@kinsomicrote) on CodePen.

Conclusion

Higher-Order Components do not have to be scary. After understanding the basics, you can put the concept to use by abstracting away functionalities that can be shared among different components.

More Resources