Re-factoring from React CreateClass to Native ES6 Classes
By Vince
For the past few weeks I have been refactoring a client’s app to move from the React.CreateClass() format to the new and shiny ES6 class based format. We also re-factored Meteor from a mixin to the new createContainer component format. While we are at it, let’s demonstrate the functional based components for a child that only uses props (so not state). Functional components make the code much cleaner as you don’t need constructors for just display logic.
React does publish a scripting package which does some of this work for you, but with the Meteor components mixed in, that was risky to use. It’s also a good time to do a code audit and make sure there are no hidden bugs that might bite down the road (found quite a few).
Here are the main changes for React, we will cover Meteor in another article.
Before:
import React from 'react';
//CHILD Component (normally this is in a separate file and you import it, but for brevity its in one)
const ChildComponent = React.createClass({
displayName: 'Child',
propTypes: {
showSomethingProp: React.PropTypes.bool,
},
getDefaultProps() {
return {
showSomethingProp: false,
};
},
render(){
return (
<div>
{this.props.showSomethingProp ? <div>Why Hello There!</div> : null }
</div>
);
}
});
//MAIN APP
const App = React.createClass({
displayName: 'App',
getInitialState () {
return {
showSomething: false,
};
},
handleSomething(event){
event.preventDefault();
this.setState({
//set the opposite state
showSomething: !this.state.showSomething,
});
},
componentDidMount() {
console.log('Component App Mounted');
},
render(){
return (
<div className="myclass">
<ChildComponent
showSomethingProp={this.state.showSomething}
/>
<button onClick={this.handleSomething}>Click Me</button>
</div>
);
}
});
export default App;
So the app doesn’t do much, but it has a button that when you click on it, will show or hide text in the child component. This demonstrates State, and passing the application state as Props to a child. If the showSomething state value is true, then the child component will show/return:
<div>Why Hello There!</div>
Otherwise is returns just the
with nothing there.We are going to re-factor App into an ES6 class, but ChildComponent doesn’t need to do much but return a value, so that can be converted to a “Functional Component”, which is just a function that takes in an argument (props in this case) and returns some JSX code. This keeps it simple and you don’t need all the extra React boilerplate. If you look at the react-router examples, most of them are functional components.
Here is the completed code, below we will run through what the differences are.
import PropTypes from 'prop-types';
import React from 'react';
//CHILD
const ChildComponent = (props) => {
return (
<div>
{props.showSomethingProp ? <div>Why Hello There!</div> : null }
</div>
);
};
ChildComponent.propTypes = {
showSomethingProp: PropTypes.bool,
};
ChildComponent.defaultProps = {
showSomethingProp: false,
};
//APP
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
showSomething: false,
};
this.handleSomething = this.handleSomething.bind(this);
}
componentDidMount() {
console.log('Component App Mounted');
}
handleSomething(event) {
event.preventDefault();
this.setState({
//set the opposite state
showSomething: !this.state.showSomething,
});
}
render() {
return (
<div className="myclass">
<ChildComponent
showSomethingProp={this.state.showSomething}
/>
<button onClick={this.handleSomething}>Click Me</button>
</div>
);
}
}
export default App;
So let’s start with this line:
import PropTypes from 'prop-types';
React has moved the prop checking away from their main library to an npm library called prop-types. It has the same functionality (and more), so not much of a change here. Just need to make sure you “npm install prop-types -save” it. The change comes in how you check your props:
//New:
PropTypes.bool
//Old:
React.PropTypes.bool
Pretty easy change.
Now let’s talk about functional components:
const ChildComponent = (props) => {
return (
<div>
{props.showSomethingProp ? <div>Why Hello There!</div> : null }
</div>
);
};
What we are doing here is creating an ES6 style function, called a “Fat Arrow” and having it return some JSX. The input we are sending the function is called props (you can call it whatever you want but props makes it easier to understand). All we do is see if the prop showSomethingProp is true (and it exists), if so we return the “Why Hello There!” text, if not we return null. We are using a JavaScript Tertiary operator to check this.
You can also check the incoming prop types for Functional Components:
ChildComponent.propTypes = {
showSomethingProp: PropTypes.bool,
};
ChildComponent.defaultProps = {
showSomethingProp: false,
};
Here we are making sure the showSomethingProp is of type bool (you can also make it required by adding .isRequired after bool, so “PropTypes.bool.isRequired”). We are also setting the default value to false, just in case one isn’t sent in. For this example it doesn’t matter, since if it’s undefined that will also fail the test and not return anything.
Now into the class syntax for React Components:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
showSomething: false,
};
this.handleSomething = this.handleSomething.bind(this);
}
You can see here the main change is we are using the React.Component class, and creating our own version of it called “App”. We need to call the constructor to build our class, and inside the constructor is where we create our default state, and connect any bindings. The super command calls the React.Component’s own constructor, that way we initialize the outside component, and then we create our component on top of that.
The “this.state” line replaces the old getInitialState() function. Now we just declare this.state as a normal JavaScript objects right when we create our App class/component.
The other line, this.handleSomething = this.handleSomething.bind(this), tells our code that this.handleSomething should use the “this” context from our main class. This is the cleanest way in my opinion to connect your functions to the class. This is the way JavaScript works, it needs a binding somewhere. You can read more about this on Facebooks page here: Handling Events. If you want to read more about “this” and what context means, the best resource I have found is this: You Don’t Know JS.
For the React LifeCycle functions:
componentDidMount() {
console.log('Component App Mounted');
}
They stay the same. The only difference is you don’t need a semi-colon at the end of the function call (technically we almost never need semi-colons but the scripter in me will not let that go).
The no semi-colon principle is the same for the render function as well.
That’s it as far as major changes go. You can change over an app pretty quickly, but might as well take the time to clean up your code with eslint and a style guide (I like airbnb). If you don’t know what I mean, read more on that here: AirBnb Config