In this article, we will be diving deep into the basics of higher-order components(HOC) in React. We will cover the concept, syntax, and application of the HOCs. We will also go through some common problems developers face related to React’s HOCs.
What are Higher-order components(HOC)?
A higher-order component in React is used when the components need to share common functionality. It is a pattern where a function takes a component as an argument and returns a new component. In simple code, it would be something like:
const NewComponent = higherOrderComponent( OrgininalComponent)
We pass in the original component as an argument into the higher-order component, which ultimately gives the new modified component. Typically, HOC adds additional data or functionality to the original component.
When should you see them?
Suppose our client wants us to create a component that increases the variable count every time its clicks. The code for the same is as follows:
import React, { Component } from 'react'
export class ClickCounter extends Component {
constructor(props) {
super(props)
this.state = {
count : 0
}
}
incrementCount = () =>{
this.setState(prevState =>{
return { count: prevState.count+1}
})
}
render() {
const {count}=this.state
return (
<div>
<button onClick={this.incrementCount}> Clicked {count} times</button>
</div>
)
}
}
export default ClickCounter
Now the client expresses his other requirement of wanting a component that increases the variable count on hovering. The code for it is as follows:
import React, { Component } from 'react'
export class HoverCounter extends Component {
constructor(props) {
super(props)
this.state = {
count : 0
}
}
incrementCount = () =>{
this.setState(prevState =>{
return { count: prevState.count+1}
})
}
render() {
const {count}=this.state
return (
<div>
<h2 onMouseOver={this.incrementCount}> Hovered {count} times</h2>
</div>
)
}
}
export default HoverCounter
Now, if we look at both components, the code is duplicated, and the functionality is not reused. We have the counter functionality in both components and should have reused it. So if ten different components needed the counter functionality, we would have been writing the same code repeatedly.
The question now is how we can reuse this code.
The immediate thought is to lift this state to the parent component and pass down the handler as a prop. This would work in our scenario where the components are the children of the same parent but imagine a scenario where counter components are scattered in the react component tree.
Here there are better solutions than lifting the state. This is where higher-order components come into the picture. Developers can reuse the code logic in different components. This makes our code less repetitive, optimized and more readable.
Syntax
According to React’s official documentation, it is a function that takes a component and returns a new component. which can also be portrayed as:
const EnhancedComponent = higherOrderComponent(WrappedComponent);
Here,
EnhancedComponent : It is the new modified component.
higherOrderComponent : The function which modifies the original component and has the reusable code.
WrappedComponent : The original component in which the code is to be reused.
[Also Read, React JS Tutorial for Beginners]
Using Higher-order component(HOC)
Sharing data through props
Let’s start with sharing props within the projects-wrapped component through our HOC, i.e., withCounter.js.
Let us add a name prop in our HOC while creating the new component.
import React from 'react'
const WithCounter = OriginalComponent => {
class NewComponent extends React.Component
{
render(){
return <OriginalComponent name ="Explore UI"/>
}
}
return NewComponent
}
export default WithCounter
To make these props available in our child components, we must make the following changes.
//In HoverCounter.js
import React, { Component } from 'react'
import WithCounter from './withCounter'
export class HoverCounter extends Component {
render() {
return (
<div>
<p><strong> {this.props.name} </strong> is a prop value accessed from the HOC</p>
</div>
)
}
}
export default WithCounter(HoverCounter);
Now render the HoverCounter component in App.js
import React, { Component } from 'react'
import HoverCounter from './HoverCounter'
class App extends Component {
render() {
return (
<div className='App'>
<HoverCounter/>
</div>
)
}
}
export default App
Let us now have a look at the output:
So this is how simple it is to share data among the components using HOCs.
[Also Read, Memoization Methods in React JS]
Creating and using our HOC function
Now let us start with the bare minimum code for a HOC. Create a withCounter.js file. This will be our HOC.
// withCounter.js
import React from 'react'
const WithCounter = OriginalComponent => {
class NewComponent extends React.Component
{
constructor(props) {
super(props)
this.state = {
count : 0
}
}
incrementCount = () =>{
this.setState(prevState =>{
return { count: prevState.count+1}
})
}
render(){
return <OriginalComponent
count={this.state.count}
incrementCount={this.incrementCount}
/>
}
}
return NewComponent
}
export default WithCounter
Now let us go through the above code line by line. Here we are creating a function called WithCounter, an arrow function that accepts the original component as its parameter. Now our HOC also needs to return a new component, so inside this function, we have created a new class called newComponent that returns the original component itself for now. And lastly, we return this newly created component in the arrow function.
From this HOC we are sharing the State variable count and the method incrementCount. the components wrapped in WithCounter get access to these state variables and the methods.
To use this WithCounter function in our app, we need to add a few more lines of code in the ClickCounter.js and HoverCounter.js files.
In ClickCounter.js we add:
import React, { Component } from 'react'
import WithCounter from './withCounter'
export class ClickCounter extends Component {
render() {
const {count , incrementCount}= this.props
return (
<div>
<button onClick={incrementCount}> Clicked {count} times</button>
</div>
)
}
}
export default WithCounter(ClickCounter);
//ClickCounter is now a wrapped in our HOC
Similarly in HoverCounter.js we add:
import React, { Component } from 'react'
import WithCounter from './withCounter'
export class HoverCounter extends Component {
render() {
const {count , incrementCount}= this.props
return (
<div>
<button onMouseOver={incrementCount}> Hovered {count} times</button>
</div>
)
}
}
export default WithCounter(HoverCounter);
//HoverCounter is now a wrapped in our HOC
Now lets render our components into the App.js file.
import React, { Component } from 'react'
import ClickCounter from './ClickCounter'
import HoverCounter from './HoverCounter'
class App extends Component {
render() {
return (
<div className='App'>
<ClickCounter/>
<HoverCounter/>
</div>
)
}
}
export default App
Let’s have a look at the output again:
The result is still the same as our HOC is not doing anything. Rather it’s just returning the original component. this is how we can reuse the same logic using HOC. Let’s play around with our HOC and see how we can make it work.
Passing parameters
Considers a situation where we want to increase the count by a custom value. Using HOC, we can pass specific data to the child components according to our needs. We can do this using parameters.
These are the changes we must make in withCounter.js to do so:
import React from 'react'
const WithCounter = (OriginalComponent, customIncrease) => {
class NewComponent extends React.Component
{
constructor(props) {
super(props)
this.state = {
count : 0
}
}
incrementCount = () =>{
this.setState(prevState =>{
return { count: prevState.count+ customIncrease}
})
}
render(){
return <OriginalComponent
count={this.state.count}
incrementCount={this.incrementCount}
/>
}
}
return NewComponent
}
export default WithCounter
In ClickCounter.js we add:
import React, { Component } from 'react'
import WithCounter from './withCounter'
export class ClickCounter extends Component {
render() {
const {count , incrementCount}= this.props
return (
<div>
<button onClick={incrementCount}> Clicked {count} times</button>
</div>
)
}
}
export default WithCounter(ClickCounter, 10);
Passing down props to specific components
It’s vital to keep in mind that a HOC’s child component receives props in a different way than a component that isn’t a HOC.
Take a look at the following code, for instance:
// ClickCounter.js
import React, { Component } from 'react'
import WithCounter from './withCounter'
export class ClickCounter extends Component {
render() {
const {count , incrementCount}= this.props
console.log('password': this.props.password);
return (
<div>
<button onClick={incrementCount}> Clicked {count} times</button>
</div>
)
}
}
export default WithCounter(ClickCounter, 10);
// App.js
import React, { Component } from 'react'
import ClickCounter from './ClickCounter'
class App extends Component {
render() {
return (
<div className='App'>
<ClickCounter password={"this is a secret password"} />
</div>
)
}
}
export default App
Technically we should get ‘password: this is a secret password’ in the console. But we see a different output. The value of password is assigned as undefined and not the one we assigned. This is because the ‘password’ prop is passed on to the withCounter function and not to the component we need (HoverCounter).
We can solve this problem by making the following changes in withCounter.js:
import React from 'react'
const WithCounter = (OriginalComponent, customIncrease) => {
class NewComponent extends React.Component
{
// code
render(){
return <OriginalComponent
count={this.state.count}
incrementCount={this.incrementCount}
// new change
{...this.props}
/>
}
}
return NewComponent
}
export default WithCounter
What we did here while returning the new component was we first passed down all the incoming props to the children of the higher-order component. We can notice now our problem is fixed. The value of the password is not undefined anymore. Rather it’s the one we assigned.
Conclusion
So in this article, we learned about Higher-order components(HOC) in React, when and how we should use them and their syntax. We also understood how props, hooks and parameters are shared within components using HOCs.