Higher Order Components

Tools: React

Overview

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

HOC's are useful for making reusable and dynamic components. Consider using them any time a particular functionality or feature occurs in multiple places across an application.

The goal of this post is to demonstrate how HOC's allow you to resuse code for common functionalities shared across on application.

Real-World Example

Visit the Github.com homepage and identify how many different places a toggle effect is used (dropdowns, stars, etc.). If you were to use React, how would you go about building those UI elements? Would you make individual class components that each maintain their own toggled state? That would work, but it would mean a lot of repeated code.


Guide

For this tutorial we will emulate the dropdown menu and star button found on Github.com and use a HOC to simplify each component.

Here is a simplified version of the profile dropdown Menu without an HOC:

class Menu extends Component {
  constructor() {
    super()
    this.state = {
      show: true
    }
    this.toggleShow = this.toggleShow.bind(this);
  }
  toggleShow() {
    this.setState(({ show }) => ({ show: !show }))
  }
  render() {
    const { show } = this.state;
    return (
      <div>
        <button onClick={this.toggleShow}>Menu</button>
        <nav className={show ? "show" : "hide"}>
         <h6>Signed in as Coder123</h6>
         <a>Your Profile</a>
         <a>Your Repositories</a>
         <a>Your Stars</a>
         <a>Your Gists</a>
        </nav>
      </div>
    )
  }
}

Likewise, here is a Card component which implements a starring option:

export default class Card extends Component {
  constructor() {
    super()
    this.state = {
      starred: false
    }
    this.toggleStar = this.toggleStar.bind(this);
  }
  toggleStar() {
    this.setState(({ starred }) => ({ starred: !starred }))
  }
  render() {
    const { starred } = this.state;
    return (
      <div>
        <h3>My sweet Repo</h3>
        <p>My sweet description</p>
        <button onClick={this.toggleStar}>{starred ? "Unstar" : "Star"}</button>
      </div>
    )
  }
}

Notice how both components use essentially the same state and toggle methods, differing only by name. Instead of repeating ourselves, let's abstract this functionality to one single Toggler component, which we will then reuse within Card and Menu.

class Toggler extends Component {
  constructor() {
    super()
    this.state = {
      on: false
    }
    this.toggle = this.toggle.bind(this);
  }
  toggle() {
    this.setState(({ on }) => ({ on: !on }))
  }
  render() {
    const { on } = this.state;
    const { component , ...props } = this.props;
    const C = component;
    return (
      <C on={on}toggle={this.toggle}{...props}/>
    )
  }
}

Note a few things:

  • State contains a property called on instead of show or starred. This is just to make it more generic.
  • toggle() is the generic replacement for toggleShow() and toggleStar().
  • Toggler returns a component called C, which is provided through a prop called component. This means that any component we want is effectively being 'upgraded' to contain the state and methods of Toggler.

Finally, to make this component available everywhere else we are going to write a simple HOC called withToggler and export it from the same file Toggler is written in.

The way it works is simple: Given a React component as an argument, return a functional component that returns Toggler with the component passed as props. You end up with the exact same component as before, just with toggle and on as props.

export const withToggler = C => props => <Toggler component={C}{...props}/>

We can now rewrite Card and Menu.

import {withToggler} from "./path/to/Toggler.js"

function Card({ on, toggle}) {
  return (
    <div>
      <h3>My sweet Repo</h3>
      <p>My sweet description</p>
      <button onClick={toggle}>{on ? "Unstar" : "Star"}</button>
    </div>
  )
}

//call withToggler with Card as an argument
export default withToggler(Card);
import {withToggler} from './path/to/Toggler.js'

function Menu({on, toggle}) {
  return (
    <div>
      <button onClick={toggle}>Menu</button>
      <nav className={on ? "show" : "hide"}>
       <h6>Signed in as Coder123</h6>
       <a>Your Profile</a>
       <a>Your Repositories</a>
       <a>Your Stars</a>
       <a>Your Gists</a>
      </nav>
    </div>
  )
}

//call withToggler with Menu as an argument
export default withToggler(Menu)

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

Challenges

  • Look at the sourse code of Redux and React-router and learn how they implement HOC's.
  • Come up with your own HOC's for functionalities you use often.

Summary

HOC's are functions that take React Components as arguments. They return a new component provided with additional props. HOC's maximize reusability and reduce code redundancy.

Exercises

  • TBD

Additional Resources