React and Flux: Migrating to ES6 with Babel and ESLint

*Update: The following guide has now been updated with the latest Babel v6 features.

As of June 2015, the ECMAScript 2015 (ES6) standard has been ratified for the world of JavaScript. Consequently, it’s time to get familiar with the new JavaScript syntax and use it in new projects. For this task, I’ll take my existing React/Flux Todo App and convert the front-end React and Flux components from ES5 to ES6.

Since the ES6 standard was ratified only a few months prior to the writing of this article, many JavaScript engines don’t support all the features yet. However, I can use Babel to compile the ES6 features down to ES5 automatically in my existing Webpack asset pipeline. For the actual syntax information, I can use the Airbnb JavaScript Style Guide for reference and add their ESLint configuration plugin to assist the detection of ES5 migration opportunities.

Some highlights of the new syntax are import/export for Modules, const/let instead of var, native classes, arrow functions, and string templates. Since this is a React project, I’ll include and follow the React/JSX Style Guide as well.


Improving the Webpack Pipeline

For the first step, I need to add Babel, ESLint, and the ESLint configurations with the following npm packages. For completeness, I’ll also include the normal React/Flux/Hapi/Webpack dependencies from my previous post:

# Install runtime dependencies
npm install --save react react-dom flux hapi hapi-react-views inert vision dateformat superagent babel-register babel-preset-es2015 babel-preset-react
# Install build time dependencies
npm install --save-dev webpack babel-core babel-loader babel-eslint eslint eslint-loader eslint-plugin-react eslint-config-airbnb browser-sync browser-sync-webpack-plugin

With those dependencies in place, I can add Babel and ESLint to my existing webpack.config.js file:

First, I added a preLoaders section to ensure the linting occurs before Babel transpiles the code. There I specified that the eslint-loader should look for both .jsx and .js files, only look in the assets directory, and exclude the bundle.js file generated by Babel. Next in the loaders section, I edited the test property regex to include both .js and .jsx files. I also replaced jsx-loader with babel-loader since Babel converts JSX automatically, and I excluded the node_modules folder for faster rendering. For an explanation of Webpack and the other settings, see my previous posts about serving React and Flux with Hapi and Webpack and Adding BrowserSync to Hapi and Webpack.

To finish the ESLint setup, I added this small .eslintrc configuration file to the root of the project, which simply includes the Airbnb configuration I installed earlier:

However, since I use Vim for editing and like to have an extra newline at the end of files, here’s an example of how you can override the ESLint configuration with your own rules:

Any rules explicitly stated in the .eslintrc in the root of your project will override any existing configurations you are extending.

Next, I need to make sure I have this .babelrc file at the root of my project which is required with Babel v6+:

As an optional step, I’m also going to edit my package.json scripts to add a lint script that mimics the eslint-loader settings in my Webpack config. This gives either myself or a build pipeline the option to run ESLint by itself:

“lint”: “eslint --ignore-pattern **/bundle-*.js --ext .js,.jsx assets/**”

At this point, I now have the tools in place to start using ES6 features.


Realizing the Future of JavaScript

With the new Webpack setup, I can use my previous npm development script to kick off the watching, transpiling, bundling, and linting all at once:

npm run dev

Or I can just run the linting as a one-off task with the following:

npm run lint

Either way, I’m immediately met with a slough of linting issues. In the next sections, I’ll walk through some of the more popular changes that can be expected with ES6.


Exchanging var for const and let

Immutability is highly encouraged in ES6 to help prevent side effects from changing variables, which can be the source of difficult debugging issues in any programming language. Therefore, I am charged to think about each variable declaration as either a static const or a mutable let. Perhaps the most popular error in my initial linting is this var issue returned as “unexpected var, use let or const instead.” Since the first variable errors in most of my files are require statements, I can use the new ES6 import format to prevent the variable mutation:

import React from ‘react’;
import { removeItem } from ‘../actions/TodoActions.js’;

The meaning of the first line can be directly compared to using require. On the second line, I’m specifying the import of an individual function that I’ll later export from my TodoActions.

For non-module situations, I’ll have to identify if the variable needs to change or not and use const or let appropriately. However, there is also a special situation with Flux worth mentioning. Later in this post, the ES6 version of my Flux store uses let and ESLint throws an error stating “`_store` is never modified, use `const` instead.” In this case, I know my Flux store will need to mutate, so I can’t use const. Instead, I can turn to Facebook’s Immutable.js module to create my store (perhaps in another post).


Using JavaScript classes

Another lint error involving var is using React.createClass() to create React components. The ES6 way of resolving this issue is to switch to the class extends format. In this format, the var statement is replaced by an export default class which extends the React Component class. Just like the legacy modules.export in ES5, the export keyword allows other files to import this class in ES6. Finally, the default means that I can omit the curly braces when importing it from another file.

In addition, the standard JavaScript object notation format is gone. Instead, I just list my _destroy and render functions without commas or semicolons. The ES6 and React style guides also tell me to move the render function to the bottom, rearrange some whitespace, add trailing commas, separate each component into its own file, and add React props validation. Consequently, here is a before and after of my TodoItem component:

A sample React component before migrating to ES6
A sample React component in ES6 using the React style guide

Another change to note is the constructor function introduced by ES6. With an official class structure in place, the React team decided to remove the “magical” autobinding feature of React.createClass and exchange it for allowing developers to bind their own class functions. For instance, the _delete function above will not have access to this.props.index until it is bound to the class inside the constructor with the following line:

this._delete = this._delete.bind(this);

Although this decision could cause some headaches for developers making the transition (as it did for me), it provides consistency overall for projects using both React and non-React classes.


Launching arrow functions

Perhaps one of the most anticipated ES6 features is the arrow function. Taking cues from CoffeeScript, ES6 finally acknowledges this shorthand as first class JavaScript. One good opportunity to use this feature arrives when encountering a “missing function expression name”:

However, that can also mean I am exporting a function with the legacy module.exports format and need to switch to the new import/export modular form. The RandomUserAPI module in my project provides both function format opportunities:

Before migrating to ES6
After migrating to ES6

In the above example, I’m first converting a legacy exported function into an ES6 named function. Second, I’m exchanging the anonymous callback that handles the end of the Random User API request with an arrow function. In addition, I’m choosing to leave the console statement even though ESLint throws a warning. Ideally in a large project, I would exchange console usage with a dedicated client-side logging module that could forward logs to a server module like Winston or Bunyan.


Building string templates

Another very welcome enhancement to the JavaScript language is the string template. This allows me to reflect on my PowerShell roots and embed variables inside the string statement without the frequent terminating and concatenating. In this project, the only file that has an existing example to use is the TodoStore which showcases the one-line string template (far) below:

The let variable above that defines the _store is, as I mentioned earlier, a good opportunity for Immutable.js. For now, though, I’ll leave it as it is. However, I can switch the mutative splice function in the REMOVE_ITEM section to a functional filter, which replaces the _store instead of mutating it.

An additional and especially important aspect of using the new JavaScript classes with Flux is implementing the singleton. Since I’ve converted the TodoStore from a JavaScript prototype assignment to the new ES6 class, I have to initialize the class as a singleton before registering it with the dispatcher and exporting it for use in React components. This is also true of the dispatcher which I converted to a class and instantiated in a singleton:

A sample Flux dispatcher implemented in ES6

A batteries-included component

To finish things up, I wanted to showcase a React component that contains many of the post-ES6 migration features that could be encountered in a typical project. Here are some of the highlights:

  • The constructor passes in the React props as a parameter,
  • A super keyword enables the use of the parent React.Component class methods,
  • Each class method is bound to the class so that it can be used within the other class methods, and
  • The initial state of the component is declared as a state property instead of using the legacy getInitialState function.

Regarding the absence of the popular getInitialState React function, the React team decided to replace it with the native class instance property defined inside the constructor. Although getInitialState is gone, the setState method is still used normally as it is inherited from the React.Component class.

A batteries-included React component implemented in ES6

Conclusion

Similar to my introduction to the world of Flux, forcing myself to transform a JavaScript project into ES6 provides a new level of understanding. Since JavaScript has been around for two decades, it’s a good investment to conquer the ES6 learning curve early as the changes will probably endure for many more years.

The full todo app from this post can be found on GitHub.

For an example React/Redux app in ES6, check out my React Template.

If you found this post helpful, please feel free to share it!