1. Home
  2. React
  3. Introduction to Higher-Order Components in React by example

Introduction to Higher-Order Components in React by example

Mohan Dere

Mohan Dere

Frontend developer with extensive experience in React, who loves teaching & writing.

Last updated on

If you’ve been using React for some time, you might have felt the need to have copies of the same logic in multiple components. A few use cases are:

  • Infinite scroll in three different views, all having different data
  • Components using data from third party subscription
  • App components that need logged in user data
  • Showing multiple lists(e.g. Users, Locations) with search feature
  • Enhancing different card views with same border and shadow

You might have wondered if there is a way in React to share logic across multiple components without having to rewrite it.

Well, you are right! There is an advanced technique to handle such cross-cutting concerns called Higher-Order Components (aka HOCs).

React-High-Order-16x9-1180x664-46m2n

In this article, we will understand the HOCs in depth, when to use the pattern and cover different use cases with real-life examples.

Let’s start by understanding how this pattern evolved from functional programming.


Table of Contents


Introduction to Functional Programming

One trend that has started gaining some serious traction in the past couple of years is functional programming (FP).

The traditional object-oriented approach encourages decomposition of a program into ‘objects’ that relate to a particular domain. In contrast, a functional approach guides the developer to decompose a program into small functions, which are then combined to form an application.

React and in fact, modern JS utilizes a lot of FP techniques alongside imperative code such as purity, side-effects, idempotent, function composition, closures, higher-order functions, currying, lazy evaluation etc.

Before moving further, I suggest you take a look at pure functions and side-effects from FP.  This link will prove helpful in understanding the basis of functional programming.

Let’s now delve into understanding higher order functions and higher order components with the help of examples.


A Taste of Higher-Order Functions in Functional Programming

A Higher-Order Function takes a function as an argument and/or returns a function.

React’s Higher Order Component is a pattern that stems from React’s nature that privileges composition over inheritance.

Consider this example –

// Example #1

const twice = (f, v) => f(f(v))
const add3 = v => v + 3
twice(add3, 7) // 13

// Example #2

const filter = (predicate, xs) => xs.filter(predicate)
const is = type => x => Object(x) instanceof type
filter(is(Number), [0, '1', 2, null]) // [0, 2]

// Example #3

const withCounter = fn => {
  let counter = 0
  return (...args) => {
    console.log(`Counter is ${++counter}`)
    return fn(...args)
  }
}

const add = (x, y) => x + y
const countedSum = withCounter(add)
console.log(countedSum(2, 3))
console.log(countedSum(2, 1))

// Output -
// Counter is 1
// 5
// Counter is 2
// 3

What are Higher Order Components?

A higher-order component is a function that takes a component and returns a new component.

React’s Higher Order Component is a pattern that stems from React’s nature that privileges composition over inheritance.

Consider this example –

import React from 'react'

const higherOrderComponent = WrappedComponent => {
  class HOC extends React.Component {
    render() {
      return <WrappedComponent />
    }
  }
  return HOC
}

In the above example, higherOrderComponent is a function that takes a component called WrappedComponent as an argument. We have created a new component called HOC which returns the <WrappedComponent/> from its render function. While this actually adds no functionality, it depicts the common pattern that every HOC function will follow.

We can invoke the HOC as follows:

const SimpleHOC = higherOrderComponent(MyComponent);

A higher-order component transforms a component into another component. Note that a HOC doesn’t modify the input component. Rather, a HOC composes the original component by wrapping it in a container component.

A HOC is a pure function with zero side-effects.


Coding a practical Higher-Order Component

Let’s say we want to create a list of products with a search feature. We would like to store our products array in a flat file and load it as a separate component as shown below –

import products from ‘./products.json’

Creating our first component

Let’s start by creating our first component `ProductCard`. This component is a functional component that handles the presentation of our data. The data (product) will be received via props, and each product will be passed down to the `ProductCard` component.

const ProductCard = props => {
  return (
    <div className="product">
      <p>
        <b>Title:</b> {props.title}
      </p>
      <p>
        <b>Style:</b> {props.style}
      </p>
      <p>
        <b>Price:</b> {props.price}
      </p>
      <p>
        <b>Description:</b> {props.description}
      </p>
      <p>
        <b>Free shipping:</b> {props.isFreeShipping}
      </p>
      <hr />
    </div>
  );
};

Rendering product list

Now, we need a component that will iterate over data (products) using `.map()` function. Let’s call this component `ProductsList`.

This component will render the data in a `ProductCard` component.

const ProductsList = (props) => {
  let { data: products } = props;
  return (
    <div>
      <div>
        <div>
          <h2>Products</h2>
        </div>
      </div>
      <div>
        {products.map((product) => <ProductCard key={product.sku} {...product} />)}
      </div>
    </div>
  )
}

Product list with search functionality

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 is a stateful component with user input stored in state value called `searchTerm`. Let’s call it `ProductsListWithSearch`.

import products from './products.json'

class ProductsListWithSearch extends React.PureComponent {
  state = {
    searchTerm: ''
  }
  handleSearch = event => {
    this.setState({ searchTerm: event.target.value })
  }
  render() {
    const { searchTerm } = this.state
    let filteredProducts = filterProducts(searchTerm);
    return (
      <>
        <input onChange={this.handleSearch} value={this.state.searchTerm} type="text" placeholder="Search" />
        <ProductsList products={filteredProducts} />
      </>
    )
  }
 

const filterProducts = (searchTerm) => {
  searchTerm = searchTerm.toUpperCase()
  return products.filter(product => {
    let str = `${product.title} ${product.style} ${product.sku}`.toUpperCase();
    return str.indexOf(searchTerm) >= 0;
  })
}

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`.

Now what if we want to render a list of users with search functionality too?

We are going to create a new component similar to the one above. Let’s call it `UsersListWithSearch`.


Converting our ProductsListWithSearch into HOC

Now you may notice that we will have to repeat our search logic in both components. This is where we need a Higher Order Component. Let’s create a HOC called `withSearch`.

const withSearch = WrappedComponent => {
  class WithSearch extends React.Component {
    state = {
      searchTerm: ""
    };
    handleSearch = event => {
      this.setState({ searchTerm: event.target.value });
    };

    render() {
      let { searchTerm } = this.state;
      let filteredProducts = filterProducts(searchTerm);
      
      return (
        <>
          <input
            onChange={this.handleSearch}
            value={searchTerm}
            type="text"
            placeholder="Search"
          />
          <WrappedComponent data={filteredProducts} />
        </>
      );
    }
  };
  WithSearch.displayName = `WithSearch(${getDisplayName(WrappedComponent)})`;
  return WithSearch;
};

const getDisplayName = (WrappedComponent) => {
  return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}


// Render out products list with search feature
const ProductsListWithSearch = withSearch(ProductsList);

function App() {
  return (
    <div className="App">
      <ProductsListWithSearch />
    </div>
  );
}

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

In the above code, we first imported the higher-order component. Then we added a filter method to filter the data based on what the user enters in the search input. Finally, we wrapped it with the `withSearch` component.

That’s it! We have a fully functional HOC in action!

Check out the complete example in the codepen below –

See the Pen React Hoc by Mohan Dere (@mohandere) on CodePen.


Debugging HOCs

Debugging HOCs can be hard as we can have multiple instances on the same HOC on the same page.

To tackle this, we can choose a better display name which communicates that it is the result of a HOC. For example, if your higher-order component is named WithSearch, and the wrapped component’s display name is ProductsList, you should use the display name WithSearch(ProductsList) as shown below:

WithSearch.displayName = `WithSearch(${getDisplayName(WrappedComponent)})`;

Let’s quickly compare the results with and without `displayName` prop in React Developer Tools.

Without displayName

without-display-name-v246c

With displayName

with-display-name-xz49t

Caveats


Don’t Use HOCs inside the render method

React’s reconciliation process uses component identity to decide whether it should update the existing subtree or mount a new one between two renders. This means component identity should be consistent across renders.

Also, when a component’s state changes, React has to calculate if it is necessary to update the DOM. It does this by creating a virtual DOM and comparing it with the current DOM. In this context, the virtual DOM will contain the new state of the component.

render() {
  // A new version of ProductsListWithSearch is created on every render
  const ProductsListWithSearch = withSearch(ProductsList);
  // That causes the entire subtree to unmount/remount each time!
  return <ProductsListWithSearch />;
}

If you are using HOCs inside the render method, then the identity of HOCs cannot be preserved across render and that affects the overall app performance.

But the problem here isn’t just about performance – remounting a component causes the state of that component and of all of its child components to be lost.

Instead, we should apply HOCs outside the component definition so that the resulting component is created only once.


Static methods must be copied over

Using static method on a React component isn’t useful especially if you want to apply HOC on it. When you apply a HOC to a component, it returns a new enhanced component. In effect, the new component does not have any of the static methods of the original component.

To solve this, you could use hoist-non-react-statics to automatically copy all non-React static methods:

import hoistNonReactStatic from 'hoist-non-react-statics';

const withSearch = (WrappedComponent) => {
  class WithSearch extends React.Component {/*...*/}
  hoistNonReactStatic(EithSearch, WrappedComponent);
  return WithSearch;
}

// Define a static method
ProductsList.staticMethod = function() {/*...*/}
// Now apply a HOC
const ProductsListWithSearch = withSearch(ProductsList);

// The enhanced component has no static method
typeof ProductsListWithSearch.staticMethod === 'function' // true
// Define a static method
ProductsList.staticMethod = function() {/*...*/}
// Now apply a HOC
const ProductsListWithSearch = withSearch(ProductsList);

// The enhanced component has no static method
typeof ProductsListWithSearch.staticMethod === 'undefined' // true

Refs are not passed through

You may want to pass all props to the wrapped component. However, you need to pay attention to refs since they are not passed through. This is because ref is not really a prop — like key, it’s handled specially by React.

The solution for this problem is to use the `React.forwardRef` API.


Conclusion

At the most basic level, React Higher-Order Component is the function that returns a class and is the implementation of functional programming principles in your codebase. It is also possible to pass higher order components into other higher order components since typically they take components as input and return other components as output. This can introduce unnecessary nesting into your component tree though.

Libraries such as Redux’s connect, react-router’s withRouter are good examples of why we should think about implementing higher order components.

We use higher order components to primarily reuse logic in React apps. However, they have to render some UI. Hence, HOCs are inconvenient when you want to share some non-visual logic. In such a case, React hooks seem to be a perfect mechanism for code reuse.

// Related Blogs

// Find jobs by category

You've got the vision, we help you create the best squad. Pick from our highly skilled lineup of the best independent engineers in the world.

Copyright @2024 Flexiple Inc