Apart from the discrepancies regarding the syntax, there are some major differences that we have to keep in mind when you decide to use one or another.
Let's go through all of them in detail so you can have all the information you need to choose the best way for the needs of your team and your projects.
Props
The first difference is in how we can define the props that a component expects to receive and the default values for each one of the props.
We will see how props work in detail further in this chapter, so let's now concentrate on how we can simply define them.
With createClass, we declare the props inside the object that we pass as a parameter to the function, and we use the getDefaultProps function to return the default values:
const Button = React.createClass({ propTypes: {
text: React.PropTypes.string, },
getDefaultProps() { return {
text: 'Click me!', } }, render() { return <button>{this.props.text}</button> }, })
As you can see, we use the propTypes attribute to list all the props that we can pass to the component.
We then use the getDefaultProps function to define the values that the props are going to have by default and which will be overwritten by the props passed from the parent, if they are present.
To achieve the same result using classes, we have to use a slightly different structure:
class Button extends React.Component { render() { return <button>{this.props.text}</button> } } Button.propTypes = { text: React.PropTypes.string, } Button.defaultProps = { text: 'Click me!', }
Since Class Properties are still in draft (they are not part of the ECMAScript standard yet), to define the properties of the class we have to set the attributes on the class itself after it has been created.
As you can see in the example, the propTypes object is the same we used with createClass.
When it comes to setting the default props instead, we used to use a function to return the default properties object, but with classes we have to define a defaultProps attribute on the class and assign the default props to it.
The good thing about using classes is that we just define properties on the JavaScript object without having to use React-specific functions such as getDefaultProps.
State
Another big difference between the createClass factory and the extends
React.Component method, is the way you define the initial state of the components. Again, with createClass we use a function, while with the ES2015 classes we set an attribute of the instance.
Let's see an example of that:
const Button = React.createClass({ getInitialState() {
return {
text: 'Click me!', } }, render() { return <button>{this.state.text}</button> }, })
The getInitialState method expects an object with the default values for each one of the state properties.
However, with classes we define our initial state using the state attribute of the instance and setting it inside the constructor method of the class:
class Button extends React.Component { constructor(props) {
super(props) this.state = { text: 'Click me!',
}
render() {
return <button>{this.state.text}</button> }
}
These two ways of defining the state are equivalent but, again, with classes we just define properties on the instance without using any React-specific APIs, which is good.
In ES2015, to use this in sub-classes, we first must call super. In the case of React we also pass the props to the parent.
Autobinding
createClass has a cool feature that is pretty convenient but it can also hide the way JavaScript works which is misleading, especially for beginners. This feature lets you create event handlers assuming that, when they get called, this references the component itself. We will see how event handlers work in Chapter 6, Write Code for the Browser. For now, we
are only interested in the way they are bound to the components we are defining. Let's start with a simple example:
const Button = React.createClass({ handleClick() {
console.log(this) },
render() {
return <button onClick={this.handleClick} /> },
})
With createClass, we can set an event handler in this way and rely on the fact that this inside the function refers to the component itself. Because of this we can, for example, call other methods of the same component instance. Calling this.setState() or any other functions would work as expected.
Let's now see how this works differently with classes, and what we can do to create the same behavior. We could define a component in the following way, extending
console.log(this) }
render() {
return <button onClick={this.handleClick} /> }
}
The result would be a null output in the console when the button is clicked. This is because our function gets passed to the event handler and we lose the reference to the component. That does not mean that we cannot use event handlers with classes, we just have to bind our functions manually.
Let's see what solutions we can adopt and in which scenario we should prefer one or another.
As you probably know already, the new ES2015 arrow function automatically binds the current this to the body of the function.
So for example this snippet:
() => this.setState()
Gets transpiled into the following code with Babel:
var _this = this; (function () {
return _this.setState(); });
As you can imagine, one possible solution to the autobinding problem is using the arrow function, let's see an example:
class Button extends React.Component { handleClick() {
console.log(this) }
render() {
return <button onClick={() => this.handleClick()} /> }
This would work as expected without any particular problems. The only downside is that if we care about performance we have to understand what the code is doing.
Binding a function inside the render method has, in fact, an unexpected side-effect because the arrow function gets fired every time the component is rendered (which happens multiple times during the lifetime of the application).
Firing a function inside the render multiple times, even if it is not optimal, it is not a problem by itself.
The issue is that, if we are passing the function down to a child component, it receives a new prop on each update which leads to inefficient rendering, and that represents a
problem, especially if the component is pure (we will talk about performance in Chapter 9,
Improve the Performance of Your Applications).
The best way to solve it is to bind the function inside the construction in a way that it doesn't ever change even if the component renders multiple times:
class Button extends React.Component { constructor(props) { super(props) this.handleClick = this.handleClick.bind(this) } handleClick() { console.log(this) } render() {
return <button onClick={this.handleClick} /> }
}
That's it, problem solved!