Higher Order Components in React

Higher Order Components in React

What is Higher Order Components (HOCs)?

Higher order components, or HOC's, are simply functions that take React Components as parameter, or return them as values, or both.

If you know the vanilla javascript, then you are already familiar with Higher Order Functions such as map(), reduce() and filter(). Which takes a function as an argument and/or returns a function. React's Higher Order Component is a pattern that stems from React's nature that privileges composition over inheritance. Want to know more about composition over inheritance click here.

Why use HOCs?

Simply to Promote reuse of logic across React components.

How to use HOCs?

The core structure of a HOC is a function that takes a component as the first parameter and returns a new component wrapping the first parameter. HOC are pure functions with no side-effects because the component passed in is wrapped in a new component. Typically, data is injected as a prop and additional props are appended to the component.

Example of Basic HOCs

function withExample(component) {
  const Component = component
    return function(props) {
      return <Component {…props}/>
    }
}

How do you decide to use HOCs?

First, build the component in the normal way as you do so. When the application is working as expected, review your components and identify duplicates of logic. Build the logic in a generic enough fashion to work for all existing components. By doing this, we will gain the knowledge to identify HOCs even before completing the components. But, hold back here and treat it as a refactoring step when deciding to build new HOCs.

Now we will see the full working example of HOCs. First, by create a react component named as Menu and Favorite

Menu.js

import React, {Component} from "react"

class Menu extends Component {
    state = {
        show: true
    }

    toggleShow = () => {
        this.setState(prevState => {
            return {
                show: !prevState.show
            }
        })
    }

    render() {
        return (
            <div>
                <button onClick={this.toggleShow}>
                    {this.state.show ? "Hide" : "Show"} Menu 
                </button>
                <nav style={{display: this.state.show ? "block" : "none"}}>
                    <h6>Signed in as Coder123</h6>
                    <a>Your Profile</a>
                    <a>Your Repositories</a>
                    <a>Your Stars</a>
                    <a>Your Gists</a>
                </nav>
            </div>
        ) 
    }
}

export default Menu

Favorite.js

import React, {Component} from "react"

class Favorite extends Component {
    state = {
        isFavorited: false
    }

    toggleFavorite = () => {
        this.setState(prevState => {
            return {
                isFavorited: !prevState.isFavorited
            }
        })
    }

    render() {
        return (
            <div>
                <h3>Click heart to favorite</h3>
                <h1>
                    <span 
                        onClick={this.toggleFavorite}
                    >
                        {this.state.isFavorited ? "❤️" : "♡"}
                    </span>
                </h1>
            </div>
        ) 
    }
}

export default Favorite

If we notice both the components use essentially the same state and logic to toggle methods, just differ by name. So, instead of repeating the same logic, let’s abstract this logic or functionality into a single withToggler component, which we will reuse with the Menu and Favorite components.

withToggler.js

import React, {Component} from "react"

class Toggler extends Component {
    state = {
        on: false
    }

    toggle = () => {
        this.setState(prevState => {
            return {
                on: !prevState.on
            }
        })
    }

    render() {
        const {component: C, {…props}}  = this.props
        return (
            <C on={this.state.on} toggle={this.toggle} {...this.props} />
        )
    }
}

Notes Following points here:

  1. The state has a property called on instead show and isFavorite. This will make it more generic.
  2. toggle() is also more generic method than toggleShow() and toggleFavorite().
  3. Toggler returns a component called c , which is provided through a prop called component. This means that if any component we want is effectively supercharged to contain state and methos of Toggler.

In the last, to use this component we will write and HOC component called withToggler and export it from same file where Toggler is written in.

export function withToggler(component) {
    return function(props) {
        return (
            <Toggler component={component} {...props}/>
        )
    }
}

Now we can refactor our Menu and Favorite components by removing the state and methods from both components.

Favorite.js

import React, {Component} from "react"
import {withToggler} from "./HOCs/withToggler"

function Favorite(props) {
    return (
        <div>
            <h3>Click heart to favorite</h3>
            <h1>
                <span 
                    onClick={props.toggle}
                >
                    {props.on ? "❤️" : "♡"}
                </span>
            </h1>
        </div>
    ) 
}

const SuperchargedFavoriteComponent = withToggler(Favorite)
export default SuperchargedFavoriteComponent

Menu.js

function Menu(props) {
    return (
        <div>
            <button onClick={props.toggle}>
                {props.on ? "Hide" : "Show"} Menu
            </button>
            <nav style={{display: props.on ? "block" : "none"}}>
                <h6>Signed in as Coder123</h6>
                <a>Your Profile</a>
                <a>Your Repositories</a>
                <a>Your Stars</a>
                <a>Your Gists</a>
            </nav>
        </div>
    ) 
}

export default withToggler(Menu)

Compare the refactored Menu and Card with the versions from above. The amount of code has been cut in half!

Finally you can render your both the component into App.js

App.js

import React from "react"
import Menu from "./Menu"
import Favorite from "./Favorite"

export default function App() {
    return (
        <div>
            <Menu />
            <hr />
            <Favorite />
        </div>
    )
}

That is all from me about Higher Order Component. If you find any mistakes or errors above, please mention me in the comment section. I would greatly appreciate it if you do 😊


References