Here at NMC, we’re big fans of the React library for building user interfaces in JavaScript. We’ve also been experimenting with the next version of JavaScript, ES6, and were excited to see the latest version of React promote ES6 functionality. Starting with React 0.13, defining components using ES6 classes is encouraged.
Refactoring a React 0.12 component defined using `createClass` to an 0.13 and beyond class only requires a few straightforward refactoring steps. In this blog post, we’ll walk through them one-by-one.
Step 1 – Extract `propTypes` and `getDefaultTypes` to properties on the component constructor
Unlike object literals, which the `createClass` API expected, class definitions in ES6 only allow you to define methods and not properties. The committee’s rationale for this was primarily to have a minimal starting point for classes which could be easily agreed upon and expanded in ES7. So for class properties, like `propTypes`, we must define them outside of the class definition.
Another change in React’s 0.13 release is that `props` are required to be immutable. This being the case, `getDefaultProps` no longer makes sense as a function and should be refactored out to a property on the constructor, as well.
Before:
var ExampleComponent = React.createClass({
propTypes: {
aStringProp: React.PropTypes.string
},
getDefaultProps: function() {
return { aStringProp: '' };
}
});
After:
var ExampleComponent = React.createClass({ ... });
ExampleComponent.propTypes = {
aStringProp: React.PropTypes.string
};
ExampleComponent.defaultProps = {
aStringProp: ''
};
Step 2 – Convert component from using `createClass` to being an ES6 Class
ES6 class bodies are more terse than traditional object literals. Methods do not require a `function` keyword and no commas are needed to separate them. This refactoring looks as such:
Before:
var ExampleComponent = React.createClass({
render: function() {
return <div onClick={this._handleClick}>Hello, world.</div>;
},
_handleClick: function() {
console.log(this);
}
});
After:
class ExampleComponent extends React.Component {
render() {
return <div onClick={this._handleClick}>Hello, world.</div>;
}
_handleClick() {
console.log(this);
}
}
Step 3 – Bind instance methods / callbacks to the instance
One of the niceties provided by React’s `createClass` functionality was that it automatically bound your methods to a component instance. For example, this meant that within a click callback `this` would be bound to the component. With the move to ES6 classes, we must handle this binding ourselves. The React team recommends prebinding in the constructor. This is a stopgap until ES7 allows property initializers.
Before:
class ExampleComponent extends React.Component {
render() {
return <div onClick={this._handleClick}>Hello, world.</div>;
}
_handleClick() {
console.log(this); // this is undefined
}
}
After:
class ExampleComponent extends React.Component {
constructor() {
super();
this. _handleClick = this. _handleClick.bind(this);
}
render() {
return <div onClick={this._handleClick}>Hello, world.</div>;
}
_handleClick() {
console.log(this); // this is an ExampleComponent
}
}
As a bonus step, at the end of this post we’ll look at introducing our own Component superclass that tidies up this autobinding.
Step 4 – Move state initialization into the constructor
The React team decided a more idiomatic way of initializing state was simply to store it in an instance variable setup in the constructor. This means you can refactor away your `getInitialState` method by moving its return value to be assigned to the `this.state` instance variable in your class’ constructor.
Before:
class ExampleComponent extends React.Component {
getInitialState() {
return Store.getState();
}
constructor() {
super();
this. _handleClick = this. _handleClick.bind(this);
}
// ...
}
After:
class ExampleComponent extends React.Component {
constructor() {
super();
this. _handleClick = this. _handleClick.bind(this);
this.state = Store.getState();
}
// ...
}
Conclusion
The handful of refactoring steps needed to convert an existing component to an ES6 class / React 0.13 and beyond component is pretty straightforward. While `React.createClass` is not deprecated, and will not be until JavaScript has a story for mixins, there is a strong consensus that working in the direction the language is heading is wise.
As a closing thought, consider one additional refactoring that introduces your project’s own base Component class to hold niceties that are reused through your own Component library.
Bonus Step – Refactor to a base component
Before:
class ExampleComponent extends React.Component {
constructor() {
super();
this. _handleClick = this. _handleClick.bind(this);
this. _handleFoo = this. _handleFoo.bind(this);
}
// ...
}
After:
class BaseComponent extends React.Component {
_bind(...methods) {
methods.forEach( (method) => this[method] = this[method].bind(this) );
}
}
class ExampleComponent extends BaseComponent {
constructor() {
super();
this._bind('_handleClick', '_handleFoo');
}
// ...
}
Notice how we’ve reduced the tedium of binding multiple instance methods to `this` by writing a `_bind` helper method in our `BaseComponent`. The `_bind` method uses a couple of awesome ES6 features: `methods` is a rest parameter, and there’s an arrow function in the `forEach`. If you’re unfamiliar with these features of ES6, I’ll leave them as cliffhangers for you to explore further. Happy trails.