How to Use React Higher-Order Components

Utilise React HOCs for class and functional components

Higher-Order Components (or HOCs) in React are functions that take a component and return a new component, enhancing the original in some way:

const EnhancedComponent = hoc(OriginalComponent);

HOCs are very useful for injecting functions, state, and additional data into components as props, as well as wrapping components with styling or more JSX. Using HOCs allow us to only extend the components that need to be extended, while at the same time keeping your codebase modular by separating specialised logic from component implementations.

Remember, React components are designed to be reusable — they should have one purpose and only contain the logic needed for that purpose. HOCs allow us to adhere to these expectations.

This article will break down what HOCs are and how they are used in React, with examples of how they can be applied.

HOCs at a High Level

You will find many modules adopting the HOC design pattern as a means of injecting functionality from the module into your components — this has proven to be the most popular use case of HOCs.

It turns out that the most widely adopted packages used in React adopt HOCs in this way — here are a few examples:

  • react-cookies allow us to save and load cookies from any component, the functions of which are injected with the withCookies() HOC.
  • react-redux supplies the connect() HOC that injects state and actions as props, thus giving components access to the redux store.
  • react-router-dom includes a withRouter() HOC, giving components the router history, location and nearest matched <Route />.

HOCs are commonly (but not compulsorily) prefixed with with or get. with HOCs are expected to inject functionality whereas get HOCs are expected to inject data into the original component.

In most cases you will be applying HOCs to components at export, after your original component is defined:

import { withFunctions } from 'my-module';
class OriginalComponent extends React.Component {
...
}
export default withFunctions(OriginalComponent);

Note: You can wrap any number of HOCs around a component.

So applying a HOC to a component is very simple; we just need to import the required HOC function and wrap the original component with it. The implementation of a HOC is also just as you would expect from a function; the following being our base implementation, returning an enhanced class component:

export function withFunctions(OriginalComponent) {
return class extends React.Component {
      // make some enhancements
...
      render() {
//return original component with additional props
return <OriginalComponent {...this.props} />
}
}
}

Essentially, a new class is being built around the original component without mutating the original. Concretely:

  • The original component is supplied as an argument to the HOC.
  • This HOC function returns a React class component, with the necessary enhancements you wish to make (more lifecycle methods, state, api endpoints, etc…)
  • render() returns the original component with any additional props you may wish to add, or any surrounding JSX you may wish to include.

As mentioned, it is bad practice to mutate the original component; do not change its prototype. Instead, adopt a composition pattern where the original component is wrapped in the new container component class — hence the terminology:-

  • Wrapped Component being the original component, <OriginalComponent /> in the above example
  • Container Component being the new class being defined in the HOC.

Note: The above HOC always returns a class component. If you pass a functional component into withMyFunction(), a class component will be returned. How to implement HOCs for functional components is documented further down the article.

As HOCs are just functions, they can be defined as a list of export functions in a dedicated file and imported just like any other function:

// src/hoc/index.js
import React from 'react';
export function withFunctions(WrappedComponent) {
return class extends React.Component {
...
}
}
// src/components/OriginalComponent.js
import { withFunctions } from '../hoc';
...

Let’s explore some ways we can enhance a wrapped component.

Prop Manipulation

We saw from above that passing additional props into the wrapped component is just a matter of using the spread operator in our return statement:

render() (<WrappedComponent {...this.props} />);

This is a vital first step to ensuring that <WrappedComponent /> receives any new props that are passed into the HOC via JSX.

We are not limited to only passing props via JSX though. What if the purpose of our HOC is to inject specific functions to perform a certain task — perhaps to provide API calls to a web service. In this case we’ll need to import these functions and include them as props in <WrappedComponent />:

// src/hocs/index.js
import React from 'react';
import { func1, func2 } from '../functions';
export function withFunctions(WrappedComponent) {
return class extends React.Component {

render() {
const newProps = {
func1: func1,
func2: func2
};


return <WrappedComponent {...this.props} {...newProps} />
}
}
}

Conversely, our class may have props passed to it that we do not wish to pass through to <WrappedComponent />. In this case we can filter out unneeded props, and utilise the spread operator again to group the remaining props:

...
render() {
const { unacceptedProp, byebye, ...acceptedProps } = this.props;
return <WrappedComponent {...acceptedProps} />
}

State Abstraction and Manipulation

Another use case for HOCs is to inject state into components. Doing so closely resembles what we expect from a React class component that has state. Below is an example that injects state for storing an email address, as well as a handler function to update it:

// src/hocs/index.js
import React from 'react';
export function withFunctions(WrappedComponent) {
return class extends React.Component {
      constructor(props) {
super(props);
this.state = {
email: ''
};

}
      onEmailUpdate = (event) => {
this.setState({ email: event.target.value });
}
      render() {
         return 
<WrappedComponent
{...this.props}
email={this.state.email}
onEmailUpdate={this.onEmailUpdate}
/>
}
}
}

Instead of defining a newProps object here, I have simply passed our state related props directly in WrappedComponent. We could use HOCs to inject values from a global state store:

constructor(props) {
super(props);
this.state = {
email: store.get('email') || {}
};

}
...
render() {
return
<WrappedComponent
{...this.props}
{...this.state}
onEmailUpdate={this.onEmailUpdate}
/>
}
}

Note: In the event a lot of state is defined and needed in your wrapped component, inject the entire component state as props into the WrappedComponent with the spread operator.

HOCs for Reusability

For the sake of avoiding repetition, the React docs have outlined an HOC abstraction of subscribing to a data source and injecting the fetched data into a wrapped component every time the data changes, in additional to storing it in the HOC’s state. The data source subscription itself is managed by HOC lifecycle methods.

The HOC is dubbed withSubscription(), allowing us to pass a DataSource object to fetch whatever the wrapped component is interested in, whether that be a blog post, a comment list, a live feed, logs, etc. The origin of DataSource is not documented in the React docs, and it remains a mystery how it is fetching data and listening to changes. (polling? web sockets? who knows).

But what is interesting in this case is that we pass a second argument to our HOC — A function that fetches data from an API:

// a data source for fetching a comment list
(DataSource, props) => DataSource.getComments()
//a data source for fetching a blog post
(DataSource, props) => DataSource.getBlogPost(props.id)

Note: Even though the comment list function does not use the second argument, props, in its implementation, it is always passed as a second argument within the HOC itself. To save confusion and maintain consistency, I have included it in this explanation — it is not included however in the official React docs.

So we are essentially injecting a function definition into our HOC to be used to fetch data. The componentDidMount and componentDidUnmount lifecycle methods have been introduced to handle the initialisation and removal of our listener, as well as a handleChange method that is called every time our data changes, thus committing it to the component state and re-rendering our wrapped component.

Although this use case may seem hard to wrap your head around (pun intended), the takeaways are straight forward:

  • We can inject multiple arguments into HOCs, even though a single argument is best in most cases — we will explore why further down.
  • The arguments we do pass into a HOC can be functions, that can then be called within the HOC. This is useful for linking HOCs to web services, determining what data is passed to our wrapped components.
  • Lifecycle methods can be introduced to wrapped components. Attaching event listeners or managing data subscriptions are good examples of how lifecycle methods can be utilised within a HOC.

HOCs as Utilities

Let’s now briefly visit a somewhat less intense use case for HOCs: Component metadata. This is a somewhat natural use-case for HOCs: obtaining metrics about a wrapped component that you can then inject as props.

Take react-measure or react-with-available-width as examples — HOCs that measure your component width, height, bounds, offset, and more.

Both packages come with an onResize method, allowing you to respond to changes to any of the aforementioned metrics. These metrics can be very handy for advanced UX customisation, reacting to window scrolling, resizing, and more.

Higher Order Functions

The concept of a HOC thus far takes a component and returns a new component:

Component => Component

But we can expand on this concept. What if we provided a function that returns another function, which will then be used to return the enhanced wrapped component? — thus adding another layer of functionality to the HOC:

Function()(Component) => Component 

This is exactly what Redux’s connect() does, the syntax of which looks like the following:

const EnhancedComponent = 
connect(mapStateToProps, matchDispatchToProps)(WrappedComponent);

The return value of connect() is a function itself, with WrappedComponent being the single argument passed into that resulting function.

connect() here can be termed a higher order function; we are returning an enhanced function with mapStateToProps and matchDispatchToProps, which then returns a HOC. Why is this necessary? Because, in the case of connect(), we are supplying objects containing the props and dispatch methods that determine how the Redux store will be interacted with — the result of which is passed through the HOC as props.

For a more abstract explanation, we are providing configuration options to the higher order function to determine what is injected into our enhanced component.

HOFs keep HOCs single-argument

If we examine connect()’s method signature, we actually have 4 optional parameters:

function connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)

The reason for this added functional layer is so that the HOC itself stays a single-argument function, making it easier to compose when combining HOCs together:

const EnhancedComponent = withRouter(connect(mapState, mapDispatch)(WrappedComponent))

Of course, nothing is stopping you from introducing more parameters to your HOCs for additional configuration; doing this is perfectly valid syntax:

function myHoc(WrappedComponent, config?)

But in doing so we are sacrificing the Component => Component signature associated with the HOC.

HOCs for Functional Components

So far we have been discussing HOCs that return React class components. With the advent of functional components and React Hooks taking more precedence than ever before, one might question whether HOCs can support functional components too — and they can indeed.

Instead of returning a new class, we just need to return an enhanced function. Now, doing this is more limiting than returning class components, not being able to utilise lifecycle methods. However, what we do have are React Hooks; useEffect and useState can be used as an alternative to lifecycle methods and this.state respectively.

The following example demonstrates enhancing a functional component with the useState hook, allowing the wrapped component to increment a counter supplied by the HOC.

The HOC itself injects count and setCount hook into its wrapped component as props:

//functional HOC with useState hook
import React, { useState } from 'react';
function withCountState(Wrapped) {
return function (...props) {
      const [count, setCount] = useState(0);

props['count'] = count;
props['setCount'] = setCount;
      return <Wrapped {...props} />;
}
}

Now our Wrapped component will be able to increment count supplied by withCountState(). Props are passed as an argument, as opposed to this.props coming from a class component.

Check out the simplicity of our <Wrapped /> component now:

const Wrapped = (props) =>  {
const {count, setCount} = props;
   return(
<div>
<h1>Counter Functional Component</h1>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Increment count
</button>
</div>);
};

Finally, apply the HOC to <Wrapped />:

const EnhancedWrapped = withCountState(Wrapped);

As functional components take less overhead than class components, they may be a more favourable approach where the switch can be made from a class component HOC.

Conclusion

This talk has been pro-HOC. However, there are some caveats that are worth being aware of, mainly that HOCs cannot be called from within a component’s render method due to React’s reconciliation algorithm.

The other edge cases mentioned in the docs are extreme cases that most developers will not need to consider, but its recommended familiarising yourself with them nonetheless (you may have a team member who decides to implement a static method in a wrapped component, that must be copied to a HOC).

Do you have ideas at this stage on how you could adopt more HOCs in your React projects? Perhaps splitting functionality from a bloated component, or noticing that a range of your apps adopt a similar functionality that could be abstracted to a HOC as a module.

In any case, abstracting logic to a HOC does indeed make code more manageable and readable, and ultimately makes for a better maintained project.