React provides us with a technique to reuse component logic. This is an advanced technique called higher-order component, or HOC. HOC is not a React API part. It is a function that returns a new component by taking a component as an argument and transforming a component into another component. Also, we can say that HOCs are the pattern that comes out of React’s compositional nature.
1 2
//transform a component to another component const ResultingComponent = higherOrderComponent(OldComponent);
Examples of HOC are third-party React libraries like createFragmentContainer of Relay and connect of Redux, etc.
Higher-order components help us avoid rewriting the same logic again and again for every other component by reusing the code, which in turn makes code more readable. It allows us to extend the functionality of the previously available component.
Let’s say we have an average finding function that computes the average of two numbers as follows:
1
2
3
function average(x, y) {
return (x + y) / 2;
}
Now let us use the higher-order component to call the average function as follows:
1
2
3
4
5
function HOC(x, avgRef) {
return avgRef(x, 3)
}
//Call HOC
HOC(5, average) //Result: 4
We can extend our component functionalities by using higher-order components. We are using average again in HOC adding a new value along with it in the above example.
In order to check whether to update a component or not React creates a virtual DOM and compares it with the current DOM. If the virtual DOM contains a new state then it will update the component. If we use HOC inside the render method then it will affect the performance of the app since the identity of HOC will not preserve in this case. Also due to this, remounting a component will result in a loss of the state of that component and its child component. So HOC should be defined outside the component which will make sure that the component is created only once.
HOC returns a new enhanced component and that new component will not have the static methods of the previous component from which this component was created. So we need to use hoist-non-react-statics to copy our non-React static methods.
We usually pass props to our wrapped component but the same can not be said for refs. We cannot pass them through our component since refs are not props, so we can use React.forwardRef API in this case.
Say we have different items stored in a collection array. We want to list them and search through them and we will be using HOC in the search part.
First we make a file that will store our collection array. Now we need to create a functional component to list our collection items.
1
2
3
4
5
6
7
8
9
10
11
12
const Collections = items => { //here items is prop
return (
<div className="items">
<p>
<b>Title:</b> {items.name}
</p>
<p>
<b>Description:</b> {items.description}
</p>
</div>
);
};
As you can see we are passing collection items as props to represent each one of them. Now let us render our collection items one by one by looping through the collection array items.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const CollectionItems = (items) => {
let {
data: collections
} = items;
return (
<div>
<div>
<div>
<h2>Your Collections are listed below</h2>
</div>
</div>
<div>
{collections.map((collection) => <Collections key={collection.id} {...collection} />)}
</div>
</div>
)
}
Now we need to make search functionality to search through our collection list. Since we want to search using search terms, we will make a stateful component that will take user input to search collection items.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class SearchCollectionList extends React.PureComponent {
state = {
toSearch: ''
}
searchHandler = e => {
this.setState({
toSearch: e.target.value
})
}
render() {
const {
toSearch
} = this.state
let receivedCollection = getCollectionItem(toSearch);
return (
<>
<input onChange={this.searchHandler} value={this.state.toSearch} type="text" placeholder="Search" />
<Collections products={receivedCollection} />
</>
)
}
}
const getCollectionItem = (toSearch) => {
toSearch = toSearch.toUpperCase()
return collection.filter(item => { //here collection is array of collection items
let str = `{item.name} {item.description}`.toUpperCase();
return str.indexOf(toSearch) >= 0;
})
}
Now let’s say we need to use the above search component for a list of ‘useless items’; we have to repeat the same search logic in that new list search component. In such cases, we will use HOC so we don’t have to repeat our logic again. To use it we wrap the component inside another component and return a new component from it.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
const searchWithHOC = WrappedComponent => {
class searchWithHOC extends React.Component {
state = {
toSearch: ""
};
searchHandler = e => {
this.setState({
toSearch: e.target.value
});
};
render() {
let {
toSearch
} = this.state;
let receivedCollection = getCollectionItem(toSearch);
return (
<>
<input onChange={this.searchHandler} value={toSearch} type="text" placeholder="Search"/>
<WrappedComponent data={receivedCollection} />
</>
);
}
};
searchWithHOC.collectionName = `SearchResults({getName(WrappedComponent)})`;
return searchWithHOC;
};
const getName = (WrappedComponent) => {
return WrappedComponent.collectionName || WrappedComponent.name || 'Component';
}
const CollectionItemSearched = searchWithHOC(Collection);
function App() {
return (
<div className="App">
<CollectionItemSearched />
</div>
);
}
const renderComponent = document.getElementById("root");
ReactDOM.render(<App />, renderComponent);
As you can see we have managed to avoid repeating the same logic for different components using HOC.
In conclusion, we have seen HOC which helps reduce code length by not repeating the same logic again and again. We do that by wrapping the component inside a new component and returning a new component from it.