In our previous post on Babel plugins and presets, we mentioned that there was one particular experimental JavaScript feature that is popular in the React community: property initializers.
As you'll see, the property initializers feature is popular among React developers for good reason. It allows for a much "cleaner" ES6 class component style. Let's take a look.
⚠️ As we mentioned in the Babel plugins and presets post, property initializers are still in the proposal phase and yet to be ratified for adoption into the JavaScript spec. There is some risk that this feature will be modified or dropped. However, due to its popularity, we believe the risks are minimal.
The proposal
Property initializers are detailed in the proposal "ES Class Fields & Static Properties." While an experimental feature that has yet to be ratified, this feature works so well with React that the Facebook team has written about using it internally.
Preparation
Property initializers are available in the Babel plugin transform-class-properties
. To use it, we must do two things:
- Install the plugin. We can use the following command to both install the plugin and ensure it's saved to our
package.json
:
npm install babel-plugin-transform-class-properties --save
- Add the following
.babelrc
file to our project:
// .babelrc
{
"plugins": ["transform-class-properties"]
}
With the plugin installed and Babel configured to use it, we're ready to refactor our React ES6 class components to use this feature.
If you're still experimenting with React and using
babel-standalone
to transpile your JavaScript in-line, you can use thedata-plugins
attribute on anyscript
tag to enable the feature:<script type="text/babel" data-plugins="transform-class-properties" src="./js/app.js" ></script>
babel-standalone
already includes the plugin.
Refactoring a component to use property initializers
Let's say we have a counter component. This counter allows us to increment or decrement a number:
The original component
Using vanilla ES6/ES7 JavaScript, our styled component might look something like this:
class CounterApp extends Component {
constructor() {
super();
this.state = {
count: 0
};
this.incrementCount = this.incrementCount.bind(this);
this.decrementCount = this.decrementCount.bind(this);
}
incrementCount() {
this.setState((prevState) => (
{ count: prevState.count + 1 }
));
}
decrementCount() {
this.setState((prevState) => (
{ count: prevState.count - 1 }
));
}
render() {
return (
<div className="card">
<div className="content">
<div className="header">
{this.state.count}
</div>
</div>
<div
className="ui bottom attached button"
onClick={this.incrementCount}
>
<i className="add icon" style={marginZeroStyle} />
</div>
<div
className="ui bottom attached button"
onClick={this.decrementCount}
>
<i className="minus icon" style={marginZeroStyle} />
</div>
</div>
);
}
}
The biggest hangup for most developers is that we have to bind custom component methods to the component inside constructor()
. This practice is both a little verbose and easy to miss, which leads to weird errors.
If you're unfamiliar with binding strategies for ES6 class components, check out our post that compares ES6 class components and
React.createClass
components.
With property initializers
With property initializers, we can get rid of our constructor()
function in this example component entirely.
We can declare state
outside of the constructor()
at the top of the component, like this:
class CounterApp extends Component {
state = {
count: 0
};
Furthermore, we can use an arrow function to declare our methods incrementCount()
and decrementCount()
. This will ensure this
is bound to the component inside of those methods, as expected:
incrementCount = () => {
this.setState((prevState) => (
{ count: prevState.count + 1 }
));
};
decrementCount = () => {
this.setState((prevState) => (
{ count: prevState.count - 1 }
));
};
Now we don't need to perform any manual binding.
In full, our refactored component (excluding render()
):
class CounterApp extends Component {
state = {
count: 0
};
incrementCount = () => {
this.setState((prevState) => (
{ count: prevState.count + 1 }
));
};
decrementCount = () => {
this.setState((prevState) => (
{ count: prevState.count - 1 }
));
};
In sum, we can use property initializers to make two refactors to our React components:
- We can define the initial state outside of
constructor()
- We can use arrow functions for custom component methods (and avoid having to bind
this
)
Using ES6/ES7 with additional presets or plugins like we do in this post is sometimes referred to by the community as "ES6+/ES7+".
Because you found this post helpful, you'll love our book — it's packed with over 800 pages of content and over a dozen projects, including chapters on React fundamentals, Redux, Relay, GraphQL, and more.