Intro to

Higher Order Components

Joe Buza

Overview

  • Problem

  • Definition

  • Patterns

  • Pros/Cons

  • Example

Problem



class AwesomeButton extends React.Component {
  componentWillMount() {
    const startTime = Date.now();
    this.setState({ startTime });
    console.log('Mounting ', startTime);
  }
  componentDidMount() {
    const endTime = Date.now();
    console.log('Mounted ', endTime);
    console.log('Total Time ', endTime - this.state.startTime);      
  }

  render() {
    return <button>Sick button</button>;
  }
}

Solution



const monitor = (Component) =>
  class extends React.Component {
    componentWillMount() {
      const startTime = Date.now();
      this.setState({ startTime });
      log(`${Component.name} MOUNTING ${startTime}`);
    }
    componentDidMount() {
      const endTime = Date.now();
      log(`${Component.name} MOUNTED ${endTime}`);
      log(`${Component.name} TOTAL TIME ${endTime - this.state.startTime}`);        
    }

    render() {
      return <Component {...this.props} />;
    }
  }

Usage


@monitor
class AwesomeButton extends React.Component {
  render() {
    return (
     	<button>Sick button</button>
    );
  }
}
class AwesomeButton extends React.Component {
  render() {
    return (
      <button>Wow this is a great button</button>
    );
  }
}

const WrappedButton = monitor(AwesomeButton);

Or

What are HOCs?

A function that takes in a component and returns an enhanced component

Patterns

  • Props Proxy

  • Inheritance Inversion

Props Proxy

  • Manipulate props

  • Abstracting state

  • Wrap with other elements



function ppHOC(WrappedComponent) {
  return class PP extends React.Component {
    render() {
      return <WrappedComponent {...this.props}/>
    }
  }
}


const makeUpperCase = (WrappedComponent) => {
  return class UpperCaseComponent extends React.Component {
    render() {
      const props = Object.assign({}, this.props, {
        title: this.props.title.toUpperCase()
      });

      return <WrappedComponent { ...props } />
    }
  };
}

Manipulating Props


const UpperCaseComponent = makeUpperCase(BaseComponent)

const makeToggleable = WrappedComponent => {
  return class ToggleableComponent extends React.Component {
    state = { toggled: false }

    toggle = () => {
      this.setState({ toggled: !this.state.toggled })
    }

    render() {
      const props = { ...this.props, toggledOn: this.state.toggled }
      return <WrappedComponent {...props} onClick={this.toggle} />
    }
  }
}

Abstracting State


const ToggleableComponent = makeToggleable(BaseComponent)
function withRedTheme(WrappedComponent) {
  return class extends React.Component {
    render() {
      return (
        <div style={{display: 'block', background: 'red'}}>
          <WrappedComponent {...this.props}/>
        </div>
      )
    }
  }
}

Wrapping other elements

Inheritance Inversion

  • Render highjacking

  • Modifying Tree

  • Manipulating State



function iiHOC(WrappedComponent) {
  return class Enhancer extends WrappedComponent {
    render() {
      return super.render()
    }
  }
}

function withPermission(WrappedComponent) {
  return class Permission extends WrappedComponent {
    render() {
      if (this.props.loggedIn) {
        return super.render()
      } else {
        return null
      }
    }
  }
}

Render Highjacking


function iiHOC(WrappedComponent) {
  return class Enhancer extends WrappedComponent {
    render() {
      const elementsTree = super.render()
      let newProps = {}
      if (elementsTree && elementsTree.type === 'input') {
        newProps = { value: 'may the force be with you' }
      }
      const props = Object.assign({}, elementsTree.props, newProps)
      const newElementsTree = React.cloneElement(
        elementsTree,
        props,
        elementsTree.props.children
      )
      return newElementsTree
    }
  }
}

Modifying Tree

export function IIHOCDEBUGGER(WrappedComponent) {
  return class II extends WrappedComponent {
    render() {
      return (
        <div>
          <h2>HOC Debugger Component</h2>
          <p>Props</p> <pre>{JSON.stringify(this.props, null, 2)}</pre>
          <p>State</p><pre>{JSON.stringify(this.state, null, 2)}</pre>
          {super.render()}
        </div>
      )
    }
  }
}

Manipulate State

Pros

  • DRYer code

  • Modular code

  • Functional programming

Cons

  • Overriding props

  • Order matters

  • Maintainability



import React, {Component} from 'react'
import userStore from './UserStore'

const renderIf = predicateFn => (WrappedComponent, failFn) =>
  class RenderIfComponent extends Component {
    render() {
      return predicateFn(userStore.modules) ? (
        <WrappedComponent {...this.props} />
      ) : (
        failFn ? failFn(this.props) : null
      )
    }
  }

export default renderIf

How could we use HOC?



import React, {Component} from 'react'
import userStore from './UserStore'

export default class RenderIf extends Component {
  render() {
    const {predicateFn, children} = this.props

    return children(predicateFn(userStore.modules))
  }
}

Resources

Intro to Higher Order Components

By Joe Buza

Intro to Higher Order Components

This presentation will explain how higher order components will make your code DRYer and more modular.

  • 1,051