Speed up your React developer workflow with code generators
Tags: javascript, productivity, react, webdev
Published: 2019-08-17 13:27:59

_The article was originally posted on my [personal blog](https://claritydev.net/blog/speed-up-your-react-developer-workflow-with-code-g)._

As React developers we often need to setup new components, hook them up with the existing infrastructure or scaffold an application. That's a lot of repeated manual work, which even though doesn't happen that often, can be quite tedious and frankly, boring. The good news is that it can be easily automated with code generators. These generators can be also shared with other developers, increasing code consistency inside a team.  

In this post we'll use [plop](https://github.com/amwmedia/plop) package to setup generators that would create React component folders either from scratch or add a new component to already existing folder. The final code is available on [Github](https://github.com/Clarity-89/react-generator/tree/master/src/scripts/generator).

Assuming you already have a React app setup (I personally prefer [create-react-app](https://github.com/facebook/create-react-app) to speed up the process), we'll start by installing plop. 

```
    npm i -D plop
```
`-D` here is a shortcut for `--save-dev`.  At the same time let's add `generate` script to our package.json.
```javascript
    // package.json

    "generate": "./node_modules/.bin/plop --plopfile src/js/scripts/generator/index.js",
```
If you install plop globally (with -`g` prefix), you can use `plop` command instead of `./node_modules/.bin/plop`.

The base structure is typical for an app made with create-react-app. Additionally, each component has a folder with the component files and `index.js`, from where all the components are exported.
```
    mysite/
        src/
            components/
                Component1/
                    Component1.js
                    index.js 
            App.js
            App.css
            index.js
            index.css
```
Now we'll create `scripts` folder in the `src` directory, inside of which we'll add `generator` folder. Inside generator let's add `index.js,` where we'll setup the generator itself, named "component".
```javascript
    // index.js

    const config = require("./actions");

    module.exports = function(plop) {
      plop.setGenerator("component", config);
    };
```
We still need to add the config for the generator, which is the main part of our setup. For that, let's create `config.js` and start fleshing it out. 

If we look at the plop [documentation](https://github.com/amwmedia/plop#setgenerator), the generator config object has 3 properties:

*   `description` - short description of what this generator does
*   `prompt` - questions to collect the input from the user
*   `action` - actions to perform, based on the input

Let's start by adding the description.
```javascript
    // config.js

    /**
     * Generate React component for an app
     */

    module.exports = {
        description: "Generate a new React component"
    }
```
Well, that was easy. Now let's define the prompts, which are basically the ways to get input from the user. 
```javascript
    prompts: [
        {
          type: "list",
          name: "action",
          message: "Select action",
          choices: () => [
            {
              name: "Create component folder",
              value: "create"
            },
            {
              name: "Add separate component",
              value: "add"
            }
          ]
        },
        {
          type: "list",
          name: "component",
          message: "Select component",
          when: answer => answer.action === "add",
          choices: listComponents,
        },
        {
          type: "input",
          name: "name",
          message: "Component name:",
          validate: value => {
            if (!value) {
              return "Component name is required";
            }
            return true;
          }
        },
        {
          type: "list",
          name: "type",
          message: "Select component type",
          default: "functional",
          choices: () => [
            { name: "Functional component", value: "functional" },
            { name: "Class Based Component", value: "class" }
          ]
        }
      ],
```
The main properties of each object in the `prompts` array are `type`, `name` and `message`. If the  type of prompt is `list`, we need to provide a list of choices for it. Plop uses [inquirer.js](https://github.com/SBoudrias/Inquirer.js#examples) for prompts, so in case you want to have a deeper look at the prompt types available, check their repository.

The way prompts work, is after the input from the user is collected, it is available as a property on the argument of the prompt's methods. For example, in the first prompt above, we provide an array of choices to select from. After user selects an option, it's `value` will be available on the `action` property of the data object, because we specified the `name` of the prompt as `action`. Then in the next prompt object we can access this value in the `when` method: `when: answer => answer.action === "add"`. The `when` property basically checks if the current prompt should be shown to the user. So in this case if the user selected `add` action, the next prompt will ask to specify a directory to which a component should be added. 

You'll notice that `listComponents` utility function is used here to get an array of component names in `components` directory.
```javascript
    // listComponents.js

    const fs = require("fs");
    const path = require("path");

    module.exports = () => {
      return fs.readdirSync(path.join(__dirname, `../../components`));
    };
```
Additionally, we use `validate` to make sure that the user has actually specified component's name. In the last prompt we ask to select the type of component to be created, providing the option of functional component as a default one, since it probably will be used the most often.

Now comes the most interesting part of the generator - its actions. Actions can be a list of commands to execute or a function that returns such list. In this example, we'll use the functional form since we need to do quite a bit of checks and conditional returns. 

But before that let's add one constant at the top of the file, `componentsPath`, which will save us from the trouble of updating path strings in multiple places, in case we decide to move the config elsewhere. 
```javascript
    // config.js

    const componentsPath = "../../components";

    // ...

      actions: data => {
        const target = data.action === "create" ? "properCase name" : "dir";
        let actions = [
          {
            type: "add",
            path: `${componentsPath}/{{${target}}}/{{properCase name}}.js`,
            templateFile: "./templates/{{type}}.js.hbs"
          }
        ];

        if (data.action === "create") {
          actions = [
            ...actions,
            {
              type: "add",
              path: `${componentsPath}/{{properCase name}}/index.js`,
              templateFile: "./templates/index.js.hbs"
            }
          ];
        }

        if (data.action === "add") {
          actions = [
            ...actions,
            {
              type: "append",
              path: `${componentsPath}/{{dir}}/index.js`,
              templateFile: "./templates/index.js.hbs"
            }
          ];
        }

        return actions;
      }
    }
```
`Actions` method takes a data object as an argument, which contains all the data collected by the prompts. The method needs to return array of action objects. The most important properties are:

*   `type` - what kind of operation this action will perform. Here we have actions that will create a new file, titled `add` or modify an existing file via `append`,
*   `path` - location of the created or modified component 
*   `templateFile` - a path to handlebars template used to create or modify a file. Alternatively a `template` property can be used, which is handy for short handlebars templates that do need to be in separate files. 

First, we fill the array with default action, which will create a new component either in directory selected from dropdown or, in case it's a new component folder, in the folder with that name. Next there are two paths - when new component folder is created we add an `index.js` file to the folder; if it's a new component file, we'll modify `index.js` with the new export. Plop has a few handy built in text transformers that we use here, namely `properCase`, which will ChangeTextToThis. Also we can use handlebars syntax to define paths to our files. These strings have access to the data from prompt, for example by doing `{{properCase name}}` we're accessing the name of the component that user typed in the prompt. Combining this with ES6 string interpolation provides a powerful way to configure our paths.

Now let's look at the templates that are used to generate and modify the files.
```handlebars
    // index.js.hbs

    export {default as {{ properCase name }}, } from "./{{ properCase name }}";

    // functional.js.hbs

    import React from 'react';
    import PropTypes from 'prop-types';

    /**
     *
     * {{ properCase name }}
     *
     */
    const {{ properCase name }} = (props) => {
      return (
        <div>
          {{ properCase name }}
        </div>
      );
    }

    {{ properCase name }}.propTypes = {};

    export default {{ properCase name }};

    // class.js.hbs

    import React, { Component }  from 'react';
    import PropTypes from 'prop-types';

    /**
    *
    * {{ properCase name }}
    *
    */
    class {{ properCase name }} extends Component {
        static propTypes = {}

        constructor(props) {
            super(props);

            this.state = {};
        }

        render() {
            return (
              <div>
                {{ properCase name }}
              </div>
            );
        }
    }

    export default {{ properCase name }};
```
We use the format `filename.js.hbs` to show the target's file type. The templates are quite simple, they are basically stubs for respective files with the component's name missing. It's worth noting that plop's helper methods are also available in the templates, which is very handy for customizing output. 

Now let's try our generator in action to verify that it actually works.

![Alt Text](https://i.imgur.com/iJIBhPA.gif)

Awesome! Generating new components is now just a command away. This is quite a simple example, however it nicely demonstrates the power of code generators. It can be easily expanded and becomes even more useful for components with a lot of boilerplate. For example if each component has some translations setup or a large list of imports. 

Got any questions/comments or other kinds of feedback about this post? Let me know in the comments here or on [Twitter](https://mobile.twitter.com/Clarity_89).