react higher order components

An attempt to explain what is an Higher-Order Component, why it is useful, why it is simple.

You may think first at Higher Order Function and you are right, it comes from Functional Programming concept where I am not expert :) and is a great alternative to the use of Mixins in the world of React.

Mixins

Before exploring what is an higher-order component (HOC), let's recap what are mixins.

Usage of mixins allows to extend a target object with some new properties or behaviours.

A popular mixin would be a logger :)

// mixin
const Logger = {
  debug(msg) {
    console.log(msg);
  },
  error(err) {
    console.log(err);
  }
};

// and why not another useful one
const AnotherMixin = {
  do() {
  }
};

Then developer team writes a scheduler service and need to use our logger mixin service.

class Scheduler {
  constructor(startDate) {
    if (startDate == null) {
      // logger call
      this.onError(new Error('startDate has to be defined'));
      return Object.create(null);
    }
    this.startDate = startDate;
  }

  start() {
    // logger call
    this.debug('start');
  }

  onError(err) {
    // logger call
    this.error(err);
  }
}

See how we apply mixin on our service.

Object.assign(Scheduler.prototype, Logger);
Object.assign(Scheduler.prototype, AnotherMixin);

new Scheduler(new Date()).start();
new Scheduler(new Date()).start();
console.log(new Scheduler(new Date()));
new Scheduler().start();

JSFiddle is here

Here I just give a trivial example, but you could take a look for more details here:

ref1 ref2 ref3

React mixin

React mixins gave us a way to enhance a component by sharing some common functionnalities with a guaranty that all of the component lifecycle methods would be called.

But, by reading that kind of article that covers all aspects of mixins, you will understand why you should avoid using it today.

You may also notice that warnings exist on Facebook React pages about mixin support...in future distributions.

Higher-order component

Ok but what is an HOC ?

"A higher-order component is just a function that takes an existing component and returns another component that wraps it."

When you hear about HOC, think about Composition.

Idea behind is to encapsulate our component and give it functionality we want, like with mixin but by using composition instead.

Code pattern

By looking at following gist by Sebastian Markbåge you may notice that what we need to do is just create a function that takes a component and return a component (wrapping input component), that is all.

const HOC = (Component) => class extends React.Component {
  constructor(props) {
    super(props);
  }
  // do whatever you may want
  render() {
    // pass new properties to wrapped component
    return <Component {...this.props} {...this.state} />
  }
};

Note that returned component extends React.Component and does not inherits from passed component.

React Mixin Refactoring Example

Let's try to refactor Facebook mixin example here with an High Order Component.

Original mixin solution

var SetIntervalMixin = {
  componentWillMount: function() {
    this.intervals = [];
  },
  setInterval: function() {
    this.intervals.push(setInterval.apply(null, arguments));
  },
  componentWillUnmount: function() {
    this.intervals.forEach(clearInterval);
  }
};

var TickTock = React.createClass({
  mixins: [SetIntervalMixin], // Use the mixin
  getInitialState: function() {
    return {seconds: 0};
  },
  componentDidMount: function() {
    this.setInterval(this.tick, 1000); // Call a method on the mixin
  },
  tick: function() {
    this.setState({seconds: this.state.seconds + 1});
  },
  render: function() {
    return (
      <p>
        React has been running for {this.state.seconds} seconds.
      </p>
    );
  }
});

JSFiddle for this mixin is here

HOC solution

All logic will be set into an HOC and wrapped component will be reduced to presentation only.

High order component would store a state, trigger state changes by applying custom function on it before.

In facebook example, our component do some stuff and repeat it on 'regular' interval and this custom logic will be passed as argument to our HOC.

// Component : the one to be wrapped
// state : initial state of our component
// intervalFn : arbitrary function modifying state

const HOC = (Component, state, intervalFn) => class extends React.Component {
  constructor(props) {
    super(props);
    this.state = state;
  }

  componentWillMount() {
    this.intervals = [];
  }

  componentWillUnmount() {
    this.intervals.forEach(clearInterval);
  }

  componentDidMount() {
    this.setInterval(this.tick.bind(this), 1000);
  }

  setInterval() {
    this.intervals.push(setInterval.apply(null, arguments));
  }

  tick() {
    this.setState(intervalFn(this.state));
  }

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

Our tiny wrapped presentation component.

class TickTock extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
        <p>
          React has been running for {this.props.seconds} seconds.
        </p>
    );
  }
}

TickTock.propTypes = {
  seconds: React.PropTypes.number.isRequired,
}

Now let's see how we can glue all this together.

First if you remember, our HOC factory function expect 3 arguments:

1) component to be wrapped "TickTock"

2) an arbitrary state object

const state = {
  seconds: 0
};

3) an arbitraty function modifying this state object

const intervalFn = (state) => {
  return {seconds: state.seconds + 1};
};

Final main result function could be

const intervalFn = (state) => {
  return {seconds: state.seconds + 1};
};

const Wrapped = HOC(TickTock, { seconds: 0 }, intervalFn);
const Wrapped2 = HOC(TickTock, { seconds: 3 }, intervalFn);
const Wrapped3 = HOC(TickTock, { seconds: 6 }, intervalFn);

ReactDOM.render(
  <div>
    <Wrapped />
    <Wrapped2 />
    <Wrapped3 />
  </div>,
  document.getElementById('container')
);

JSFiddle for this demo is here

Conclusion

HOC is an alternative to mixins that only rely on component composition.

Just for fun


Tags: ES6 Javascript React