Higher-order components(HOC) is an technique enables reuse of existing component logic.
Precisely, a higher-order component is a function that accepts a component and returns a new component. HOC can be represented as:
const newComponent = hoc(existingComponent)
Let’s now understand how it works using a detailed example
1. A Example of HOC
In this example, we will create two normal components and on HOC. The objective is to use the HOC to enhance the normal components.
The three components are:
- ClickCounter – increments a counter by one at click of a button
- HoverCounter – increments a counter by one at mouseover of text
- UpdatedComponent – this component is defined inside a file called, withCounter.js. Injects a name prop to both counters
ClickCounter.js
import React, { Component } from 'react' import UpdatedComponent from './withCounter' 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}> {this.props.name} Clicked {count} times </button> </div> ) } } export default UpdatedComponent(ClickCounter)
Notice the export statement. We now use the UpdatedComponent when exporting the normal component. In this way, the UpdatedComponent is a HOC the enhances the normal component.
HoverCounter.js
import React, { Component } from 'react' import UpdatedComponent from './withCounter' 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}> {this.props.name} Hovered {count} times </h2> </div> ) } } export default UpdatedComponent(HoverCounter)
withCounter.js
The UpdatedComponent is given below. As mentioned, the UpdatedComponent as a HOC is a function that returns an enhanced component, NewComponent. Accepts a parameter, OriginalComponent and adds a prop to it.
import React from 'react' const UpdatedComponent = (OriginalComponent)=> { class NewComponent extends React.Component { render() { return <OriginalComponent name ='Kindson' /> } } return NewComponent } export default UpdatedComponent
If you look at the example above, you’ll notice that the ClickCounter and HoverCounter component has codes that are duplicated. So we would like to move this code to the UpdatedComponent.
- Therefore, we would make the following changes:
copy the constructor and the incrementCount method into the HOC. This is the common functionality we need to share - remove these codes from ClickCounter and HoverCounter
- pass down the state and incrementCount() as state from the HOC to the original components
- then use these props in ClickCounter and HoverCounter
After making these changes, we have the following. I’ve committed the import statements.
ClickCounter
class ClickCounter extends Component { render() { //destructure the props const {count, incrementCount} = this.props; return ( <div> <button onClick={incrementCount}> Clicked {count} times </button> </div> ) } } export default UpdatedComponent(ClickCounter)
HoverCounter
class HoverCounter extends Component { render() { //destructure the props const {count, incrementCount} = this.props; return ( <div> <h2 onMouseOver={incrementCount}> Hovered {count} times </h2> </div> ) } } export default UpdatedComponent(HoverCounter)
UpdatedComponent
const UpdatedComponent = (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 UpdatedComponent
With this, we are not reusing code instead of repeating them.
2. How it Works
In the ClickCounter and HoverCounter, we are exporting the UpdatedComponent HOC passing in the original component, ClickCounter or HoverCounter.
The UpdatedComponent HOC accepts the OriginalComponent as parameter and returns a new component.
The NewComponent has a state to maintain the count property as well as a method to increment the count property. Both are passed as props to the original Components.
The original components not take this props, destructure it and renders it
3. HOC Naming Convention
- the filename and the function are usually the same. So change UpdatedComponent to withCounter.
- the original component is usually refered to as WrappedComponent. So change OriginalComponent to WrappedComponent
- the new component is normally the same as the function name, but in pascal case. So change NewComponent to WithCounter.
- in the ClickCounter and HoverCounter, change UpdatedComponent to the function name, withCounter.
You can now read the export statements as: “with Counter functionality, export the ClickCounter component”