React: ES5 (createClass) or ES6 (class)?

Dave Ceddia bio photo By Dave Ceddia Comment

When writing React, should you use the React.createClass syntax or the ES6 class syntax? Or maybe neither? This post will explain some of the differences and help you decide.

React can be written perfectly well in either ES5 or ES6.

Using JSX means that you’ll need a “build” step already, where Babel transpiles the JSX into React.createElement calls. Many people take advantage of this and just tack on es2015 to Babel’s list of transpilers, and then the entire world of ES6 is made available.

If you’re using something like Quik or React Heatpack, this is already set up for you (read quick start React if you don’t have an environment set up yet).

Compare: createClass vs class

Here’s the same component written using React.createClass and ES6 class:

var InputControlES5 = React.createClass({
  propTypes: {
    initialValue: React.PropTypes.string
  },
  defaultProps: {
    initialValue: ''
  },
  // Set up initial state
  getInitialState: function() {
    return {
      text: this.props.initialValue || 'placeholder'
    };
  },
  handleChange: function(event) {
    this.setState({
      text: event.target.value
    });
  },
  render: function() {
    return (
      <div>
        Type something:
        <input onChange={this.handleChange}
               value={this.state.text} />
      </div>
    );
  }
});
class InputControlES6 extends React.Component {
  constructor(props) {
    super(props);

    // Set up initial state
    this.state = {
      text: props.initialValue || 'placeholder'
    };

    // Functions must be bound manually with ES6 classes
    this.handleChange = this.handleChange.bind(this);
  }
  
  handleChange(event) {
    this.setState({
      text: event.target.value
    });
  }
  
  render() {
    return (
      <div>
        Type something:
        <input onChange={this.handleChange}
               value={this.state.text} />
      </div>
    );
  }
}
InputControlES6.propTypes = {
  initialValue: React.PropTypes.string
};
InputControlES6.defaultProps = {
  initialValue: ''
};

Here are the key differences:

Binding of Functions

This is probably the biggest tripping point.

With createClass, it’s easy: every property that’s a function is automatically bound by React. Refer to them as this.whateverFn wherever you need to, and this will be set correctly whenever they’re called.

With ES6 class, it’s trickier: functions are not autobound. You must manually bind them. The best place to do this is in the constructor, as in the example above.

If you want to save yourself from having to type out all those bindings manually, check out react-autobind or autobind-decorator.

Another way is to bind them inline, where you use them:

// Use `.bind`:
render() {
  return (
      <input onChange={this.handleChange.bind(this)}
             value={this.state.text} />
  );
}

// --- OR ---

// Use an arrow function:
render() {
  return (
      <input onChange={() => this.handleChange()}
             value={this.state.text} />
  );
}

Either of these will work, but they’re not as efficient. Every time render is called (which could be pretty often!) a new function will be created. It’s a little slower than binding the functions once in the constructor.

One final option is to replace the function itself with an arrow function, like this:

// the normal way
// requires binding elsewhere
handleChange(event) {
  this.setState({
    text: event.target.value
  });
}

// the ES7 way
// all done, no binding required
handleChange = (event) => {
  this.setState({
    text: event.target.value
  });
}

With this method, you don’t need to do any binding. It’s all taken care of, through the magic of arrow functions. The this inside the function will refer to the component instance, as expected.

The only caveat is that this is an “experimental” feature, meaning it’s not in the official ES6 spec. But it is supported by Babel when you enable the “stage-0” preset. If you like the syntax (which reads as “set handleChange to an arrow function that takes an event”), try it out.

Constructor Should Call super

The ES6 class’s constructor needs to accept props as an argument and then call super(props). It’s a little bit of boilerplate that createClass doesn’t have.

class vs createClass

This one is obvious. One calls React.createClass with an object, and the other uses class extending React.Component.

Pro tip: Import Component directly to save some typing if you have multiple components in one file: import React, {Component} from 'react'.

Initial State Configuration

createClass accepts an initialState function that gets called once when the component is mounted.

ES6 class uses the constructor instead. After calling super, set the state directly.

Location of propTypes and defaultProps

With createClass, define propTypes and defaultProps as properties on the object you pass in.

With ES6 class, these become properties of the class itself, so they need to be tacked on to the class after it is defined.

There’s a shortcut if your build has ES7 property initializers turned on:

class Person extends React.Component {
  static propTypes = {
    name: React.PropTypes.string,
    age: React.PropTypes.string
  };

  static defaultProps = {
    name: '',
    age: -1
  };

  ...
}

The Ninja Third Option

In addition to createClass and class, React also supports what it calls “stateless functional components.” Basically, it’s just a function, and it can’t have state, and it can’t use any of the lifecycle methods like componentWillMount or shouldComponentUpdate. Stateless functional components are great for simple components when all they do is take some props and render something based on those props. Here’s an example:

function Person({firstName, lastName}) {
  return (
    <span>{lastName}, {firstName}</span>
  );
}

That uses ES6 destructuring to pull apart the props that are passed in, but it could also be written like this:

function Person(props) {
  var firstName = props.firstName;
  var lastName = props.lastName;
  return (
    <span>{lastName}, {firstName}</span>
  );
}

Which is the Right One to Use?

Facebook has stated that React.createClass will eventually be replaced by ES6 classes, but they said “until we have a replacement for current mixin use cases and support for class property initializers in the language, we don’t plan to deprecate React.createClass.”

Wherever you can, use stateless functional components. They’re simple, and will help force you to keep your UI components simple.

For more complex components that need state, lifecycle methods, or access to the underlying DOM nodes (through refs), use class.

It’s good to know all 3 styles, though. When it comes time to look up a problem on StackOverflow or elsewhere, you’ll probably see answers in a mix of ES5 and ES6. The ES6 style has been gaining in popularity but it’s not the only one you’ll see in the wild.

Wrap Up

I hope this overview helped clear up some confusion about the different ways to write components in React.

If you’re feeling overwhelemed by all there is to learn, and looking for a path to follow, sign up below to get a downloadable Timeline for Learning React.

comments powered by Disqus