Writing a React Component in ES2015+

Josh Black
5 min readJan 20, 2016

--

Breaking down the nuances of using ES2015+ to define React Components in React 0.14.x

With the explosion in popularity of the Babel project, transpiling your JavaScript from ES2015 syntax to the ES5 that’s present in your browser is easier than ever. All it takes is a single preset, a relevant module bundler, and you’re good to go. You could start off with:

npm i babel-preset-es2015 babel-loader --save-dev

Then, inside of your .babelrc file:

{
"presets": ["es2015"]
}

And a possible corresponding webpack entry:

// ...
loaders: [
{
test: /\.js$/,
loader: 'babel'
}
]

And you’d be good to go for a vanilla JS project where you can leverage ES2015 features. If you wanted to support React, specifically its JSX syntax, you’d follow a similar process where you’d add the babel react preset to the project. You could do:

npm i babel-preset-react --save-dev

And then place it in your .babelrc file like so:

{
"presets": ["es2015", "react"]
}

And with this, Babel finally begins to understand JSX syntax like in the following example:

class App extends React.Component {
render() {
return <h1>Hello World!</h1>;
}
}

Where Babel now transforms the JSX syntax into the appropriate React.createElement call.

ES2015+

However, reading through some code that’s out there today you might run into some syntax that doesn’t really look like it’s a part of the ES2015 features that you’ve been trying to wrap your head around for the past few months.

For example, you might run into some code that looks like:

class Header extends React.Component {
static propTypes = {
title: React.PropTypes.string
}
render() {
return (
<header>
<h1>{this.props.title}</h1>
</header>
);
}
}

That uses this special static keyword for defining common pieces in React like Prop Types, or Default Props. Typically, you might be used to seeing these defined directly on a Class Definition. For example, we can take the previous example and write it as:

class Header extends React.Component {
render() {
return (
<header>
<h1>{this.props.title}</h1>
</header>
);
}
}
Header.propTypes = {
title: React.PropTypes.string
};

If you’re confused about this random static property that’s appeared, or have encountered some other strange syntax that you’re pretty sure isn’t a part of ES2015, then you’re most likely using a project that’s using features that will be a part of the ES2016 standard, or are currently proposals that are going through various stages to become actual language features.

So…what should I know if I’m working with React?

If you’re working specifically with React, the big plugin that you need to be conscious about is the transform-class-properties Babel plugin. This plugin corresponds to a proposal currently in Stage 1 that looks to add Class Fields & Static Properties to the language.

What this means to us as JavaScript developers using React is that we can define common properties inside ofClass Definitions themselves as static class properties, and we can also use a trick to get out of having to autobind any class method in the constructor function.

We can leverage the transform-class-properties plugin by just doing:

npm i babel-plugin-transform-class-properties --save-dev

And then update our .babelrc file to include the plugin that we just installed:

{
presets: ["es2015", "react"],
plugins: ["transform-class-properties"]
}

With this plugin enabled, we can define our Prop Types and our Default Props now as static class properties. For example:

class Greeting extends React.Component {
static propTypes = {
message: React.PropTypes.string
}

static defaultProps = {
message: 'Hello World!'
}
render() {
return (
<div className="greeting">
<p>{this.props.message}</p>
</div>
);
}
}

We can even take the common pattern of autobinding in our class constructor function and just assign the method as a class property that equals an arrow function. So, instead of doing the following:

class Button extends React.Component {
constructor(props) {
super(props);
this.state = { clicked: false };
this._onClick = this._onClick.bind(this);
}
_onClick() {
this.setState({ clicked: true });
}
render() {
return <button onClick={this.onClick} />;
}
}

We could just do:

class Button extends React.Component {
constructor(props) {
super(props);
this.state = { clicked: false };
}
_onClick = () => {
this.setState({ clicked: true });
}
render() {
return <button onClick={this.onClick} />;
}
}

And not even have to worry about autobinding in the constructor or even in a method call in the onClick handler for the button.

And now that we no longer have to bind our method in our constructor, we also don’t need to define this.state in our constructor function. Instead, we could just define it as a class property like so:

class Button extends React.Component {
state = {
clicked: false
}
_onClick = () => {
this.setState({ clicked: true });
}
render() {
return <button onClick={this.onClick} />;
}
}

Anything else I should know about?

The class properties plugin provided by Babel enables us to follow what the conventions of the language should be instead of having us to define properties on the Class Definition, or making us autobind methods in a constructor function.

While extremely useful, there are still even more plugins available to anyone who wants to try out more features that could make it into the JavaScript standard.

The way this standardization process will work is that language features get introduced as proposals, and then they go through 4 different stages (stage 0, stage 1, stage 2, stage 3). Proposals that have made it into stage 2 are typically seen as stable enough to include in projects, but always be aware of what you’re choosing to use!

One particularly useful stage 2 proposal is the Object Rest/Spread Property proposal. This will allow you to spread objects into JSX elements in an extremely intuitive way. For example, instead of having to pass in individual properties of an object as props to a component like:

class Warning extends React.Component {
static propTypes = {
message: React.PropTypes.shape({
title: React.PropTypes.string,
text: React.PropTypes.string
})
}
render() {
return (
<Message
title={this.props.message.title}
text={this.props.message.text}
type="error"
/>
);
}
}

We could leverage this new spread syntax:

class Warning extends React.Component {
static propTypes = {
message: React.PropTypes.shape({
title: React.PropTypes.string,
text: React.PropTypes.string
})
}
render() {
return (
<Message
{...this.props.message}
type="error"
/>
);
}
}

Wrapping Up

Of course, there are way more plugins out there for you to use and explore what works best for you and your project. And, even better, more and more proposals to the language are making it to the ECMAScript Committee stage to eventually be implemented as Babel plugins to allow us all to use the tools and language features that we want.

The plugins/presets that I mentioned above will hopefully present a huge productivity boost to you and your team as you work on conventions for your codebase, as well as providing a more clear syntax to show what exactly you’re trying to do with each line of code that you write.

So, if you’re looking to learn more about the plugins available to your through Babel, definitely check out their plugins page. Feel free to experiment and figure out works best for you. Good luck!

--

--

Josh Black

Building a design system for GitHub. Previously working on Carbon. Thoughts are my own.