Using jscodeshift with react-codemod to update createClass components to ES6 classes

A couple weeks ago, we discussed the difference between two React component styles, React.createClass and ES6 classes.

Given current trends, we decided to update our book to use components built with ES6 classes as opposed to React.createClass. Fortunately, there's tooling that made this a cinch.

jscodeshift is a toolkit for performing code-level modifications to JavaScript files. The tool enables you to go beyond basic find-and-replace. You can make major automated updates to your codebase, like changing the signature of a function and then rewrite the code accordingly everywhere that function is called.

For our purposes, we'll see how jscodeshift works by using it to update a set of example React.createClass components to ES6 classes.

react-codemod

In jscodeshift, a codemod is a transform that jscodeshift executes against your codebase. You export one or more functions that jscodeshift will invoke. Here's the example codemod that jscodeshift supplies in its README:

/**
 * This replaces every occurrence of variable "foo" with "bar"
 */
module.exports = function(fileInfo, api) {
  return api.jscodeshift(fileInfo.source)
    .findVariableDeclarators('foo')
    .renameTo('bar')
    .toSource();
}

react-codemod is a set of codemods for jscodeshift that help automate common tasks that React developers might want to perform. There are a few handy codemods in that repo. We'll be working with the transform inside transforms/class.js.

Transforming a React.createClass component

We'll transform this React.createClass() component:


const ToggleCheckbox = React.createClass({
  getInitialState() {
    return {
      checked: false
    };
  },

  toggleChecked() {
    this.setState(prevState => ({ checked: !prevState.checked }));
  },

  render() {
    const className = this.state.checked
      ? "toggle checkbox checked"
      : "toggle checkbox";
    return (
      <div className={className}>
        <input type="checkbox" name="public" onClick={this.toggleChecked} />
        <label>Subscribe to weekly newsletter</label>
      </div>
    );
  }
});

First, we need to install jscodeshift. We'll install it globally:

$ npm install -g jscodeshift

Next, we need to clone react-codemod:

$ cd ~/work
$ git clone [email protected]:reactjs/react-codemod.git

Now, we can run jscodeshift using the following syntax:

jscodeshift -t <path-to-transform> <path-to-file>

Our transform is inside react-codemod, transforms/class.js. So, after changing to the directory where our component is located, we can run the class transform like this:

$ jscodeshift -t ~/work/react-codemod/transforms/class.js ToggleCheckbox.js

Running jscodeshift with react-codemod

That magic will re-write ToggleCheckbox.js so that it now declares an ES6 class component:


class ToggleCheckbox extends React.Component {
  state = {
    checked: false
  };

  toggleChecked = () => {
    this.setState(prevState => ({ checked: !prevState.checked }));
  };

  render() {
    const className = this.state.checked
      ? "toggle checkbox checked"
      : "toggle checkbox";
    return (
      <div className={className}>
        <input type="checkbox" name="public" onClick={this.toggleChecked} />
        <label>Subscribe to weekly newsletter</label>
      </div>
    );
  }
}

react-codemod uses property initializers whenever it can.

You can run jscodeshift over a whole directory of files:

jscodeshift -t ~/work/react-codemod/transforms/class.js src/**.js

What if it skips everything?

Depending on your setup, you may run this transform on your JavaScript files and see that jscodeshift simply skipped all of them like this:

jscodeshift -t ~/work/react-codemod/transforms/class.js src/**.js

Sending 15 files to free worker...
All done.
Results:
0 errors
0 unmodified
15 skipped
0 ok

When running transforms/class.js, by default react-codemod will only modify files that explicitly import React:

import React from 'react';

If it doesn't see React being imported, it won't attempt to run the transform on the file.

If you're, say, loading the react library via script tags, you won't have these explicit require statements. So, to prevent the transform from skipping your files, pass in the flag --explicit-require=false like this:

jscodeshift -t ~/work/react-codemod/transforms/class.js src/**.js --explicit-require=false

Dry runs

You can specify you'd like jscodeshift to perform a dry run. This will prevent any of your files from being modified. Instead, you can specify that you'd like jscodeshift to print the output to the console. The -d flag specifies you'd like a dry run. Pairing it with the -p flag prints the dry run to the console:

 jscodeshift -t ~/work/react-codemod/transforms/class.js -d -p ToggleCheckbox.js

Keep your JavaScript current

JavaScript is constantly in motion and React is moving at a quick clip. Using jscodeshift, your codebase can adopt new syntax and style as the JavaScript/React ecosystems evolve.


Anthony Accomazzo

Passionate about teaching, Anthony has coached many beginning programmers through their early stages. Before Fullstack React, he led the development of IFTTT's API platform. A minimalist and a traveler, Anthony has been living out of a backpack for the past year.