Higher-Order Components (HOC) React Pattern
Karolis Ramanauskas
Posted on July 29, 2018
Today I decided to get more familiar with Higher-Order Components (HOC) commonly used in libraries such as Redux (connect
), react-i18next (translate
) or react-router (withRouter
). I have often used them as part of aforementioned libraries but never got the full grasp behind their magic. Today is the day to get underneath the carpet and see how they really work.
But before we begin, let's define what a Higher-Order Component is. According to React docs, Higher-Order Component is an advanced React pattern consisting of a function that takes a base (wrapper) component as an argument and returns a new, enhanced component. The best thing about it is that it allows to reuse component logic in a clean compositional way.
The abstract code for a HOC would look somewhat like this:
const higherOrderComponent = BaseComponent => {
// ...
// create new component from old one and update
// ...
return EnhancedComponent
}
In a more concrete form, a Higher-Order Component always takes a form similar to this:
import React from 'react';
const higherOrderComponent = BaseComponent => {
class HOC extends React.Component {
...
enhancements
...
render() {
return <BaseComponent newProp={newPropValue} {...this.props} />;
}
}
return HOC;
};
Simple example
To get a better understanding, let's take a look at a simple example of a Higher-Order Component. Imagine we want to have several components that all get set random background value each time the component is rendered.
We do this by first defining a Higher-Order Component function called withColor
. It accepts BaseComponent
as a parameter and returns the enhanced component. EnhancedComponent
that gets returned consists of a getRandomColor
and render
methods. Within the render
method we return BaseComponent
that gets assigned a new color
prop and by taking advantage of {...this.props}
desctructuring, BaseComponent
also gets access to all the props which were passed to the BaseComponent
from outside the HOC.
import React from 'react';
const withColor = BaseComponent => {
class EnhancedComponent extends React.Component {
getRandomColor() {
var letters = '0123456789ABCDEF';
var color = '#';
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
render() {
return <BaseComponent color={this.getRandomColor()} {...this.props} />;
}
}
return EnhancedComponent;
};
export default withColor;
To use the newly created HOC, we just have to export it with our base component passed as an argument.
import React from 'react';
import withColor from './withColor';
const ColoredComponent = props => {
return <div style={{ background: props.color }}>{props.color}</div>;
};
export default withColor(ColoredComponent);
Finally, we render the new enhanced component the same way as we would render a regular component.
import React, { Component } from 'react';
import ColoredComponent from './ColoredComponent';
class App extends Component {
render() {
return (
<div>
<ColoredComponent someProp="Prop 1" />
<ColoredComponent someProp="Prop 2" />
<ColoredComponent someProp="Prop 3" />
</div>
);
}
}
export default App;
Note that all the props defined at this level such as someProp
will be passed further down the line via {...this.props}
and new props such as color
in the above example are defined explicitly from within the HOC.
Practical example
The previous example was a bit contrived. We could, for example, have used a utility function that generates random color, and then call it from within each component. The end result would have been the same with less code. Let's better look at an example where Higher-Order Component pattern is indeed the most elegant solution.
In the following example we will develop a Higher-Order Component that accepts BaseComponent
and API URL to fetch the data that it needs. While the data is loading, it will show a loading state, and once the data is loaded we will display whatever BaseComponent
renders based on the data. The end result will look as follows.
To get started, we will first create a Higher-Order Component function. We will call it withLoader
and initially set the data
property of component's state to null
. Once the component has mounted we will start fetching the data and when that's done, set the data
property to the returned response.
As already mentioned, whilst the state is null
we will show a loading state. And once we have the data fetched, we will return BaseComponent
which in return will render markup based on the returned data. The Higher-Order Component function looks as follows:
import React from 'react';
const withLoader = (BaseComponent, apiUrl) => {
class EnhancedComponent extends React.Component {
state = {
data: null,
};
componentDidMount() {
fetch(apiUrl)
.then(res => res.json())
.then(data => {
this.setState({ data });
});
}
render() {
if (!this.state.data) {
return <div>Loading ...</div>;
}
return <BaseComponent data={this.state.data} {...this.props} />;
}
}
return EnhancedComponent;
};
export default withLoader;
The components that use this HOC are fairly straightforward. We simply have to take the data from the props
and use it as needed. In the example below we use the data to show a list of users.
As usual, we first need to import the Higher-Order Component function and pass the component as function argument. Along with it we also pass the API URL from which the data is fetched. See the code for a base component below:
import React from 'react';
import withLoader from './withLoader';
const Users = props => {
return (
<div>
<h1>Users:</h1>
<ul>{props.data.map(user => <li key={user.id}>{user.name}</li>)}</ul>
</div>
);
};
export default withLoader(Users, 'https://jsonplaceholder.typicode.com/users');
Similarly, we use another component to show a list of posts. The code is pretty much the same except for the component's name and API URL.
import React from 'react';
import withLoader from './withLoader';
const Posts = props => {
return (
<div>
<h1>Posts:</h1>
<ul>{props.data.map(post => <li key={post.title}>{post.title}</li>)}</ul>
</div>
);
};
export default withLoader(Posts, 'https://jsonplaceholder.typicode.com/posts/');
Now all that's left is to render the components. In order to do that we don't have to do anything special. Just import the components and render them where needed as usual. That's all 😃
import React, { Component } from 'react';
import Users from './Users';
import Posts from './Posts';
class App extends Component {
render() {
return (
<div>
<Users />
<Posts />
</div>
);
}
}
export default App;
HOC caveats 🤔
- HOC should always be a pure function. That means that the same enhanced component should always be returned with the same base component passed as a parameter.
- Don’t use HOCs inside the render method. This makes React's reconciliation algorithm think that a new component is redeclared within each render, causing the whole subtree to be unmounted rather than just checked for differences.
- Static methods do not get copied implicitly. This needs to be done explicitl. A good way to do it is with
hoist-non-react-statics
package. - Refs don’t get passed through.
Summary 🔥🔥🔥
A Higher-Order Component has access to all the default React API, including state and the lifecycle methods. This allows to reuse logic in a very conscise way and make your code more elegant. As we have seen, it can be used for a variety of use cases but sometimes it's not the most conscise solution. Sometimes a simple utility function or a smart parent component is all that's needed. So just use Higher-Order Components at your best judgement and don't over-engineer things where not needed 😊
Posted on July 29, 2018
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.