Creating a React Component Generator
Tags: react, tutorial, webdev, dx
Published: 2022-05-27 09:14:37

In this post join me as I create a generator for a React component. We will be using a really cool tool named [Plop](https://plopjs.com/) and in the end we will be able to spawn new components faster and better.

## Introduction
If you appreciate a good Dev experience (DX) and strive to maintain good coding quality/standards, having a tool for generating components scaffold code is a must. Whether you maintain a components library in your organization on not, such a tool will enable you and your colleagues to increase your dev velocity and focus on the real important aspects of component development.

Requirements
So what are we going for in this one?
Well, our generator should create the following:
* A Component folder with the name given by the developer
* An index.jsx file for the component
* An index.css file which the index.jsx file will import with basic generic rules
* An index.test.js file with a single test that checks the component’s render
* A Storybook story file which has a single story for the component

That’s quite a lot right there. Let’s get to it

***

## Plop
As mentioned above, one of the tools out there which can help us generate such code is [Plop](https://plopjs.com/). I find this tool super intuitive with fairly clear documentation.

Going by the Plop documentation, let’s first install it and make sure we can use it:

`yarn add plop -D`

This is of course a dev dependency. 

Let’s create our `plopfile.js` file right out of the example, using CJS format since the project I’m implementing this for currently does not support the ESM format:

```javascript
module.exports = function (plop) {
   // create your generators here
   plop.setGenerator('basics', {
       description: 'this is a skeleton plopfile',
       prompts: [], // array of inquirer prompts
       actions: [], // array of actions
   });
};
```

Before we pour more content to that generator, let’s see if Plop launches as expected by adding an npm script to our `package.json` and calling it:

```json
"scripts": {
       . . .
       "plop": "plop"
   },
```

Running `yarn plop` and… nothing happens. We need to introduce a generator. I’m going back to my `plopfile.js` and adding this:

```javascript
plop.setGenerator('React component generator', {
       description: 'A generator for React components',
       prompts: [
           {
               type: 'input',
               name: 'name',
               message: 'Component name',
           },
       ],
   });
```

Running plop again and I get this nice prompt asking for the component’s name. I gave it the name “Matti” but got this error:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hp7cwclmyv6makyk8t36.png) 

True. Let’s add an action.
In our action I would like to create a directory by the given component’s name, and set an empty index.jsx file in it (for now). My generator looks like this now:

```javascript
plop.setGenerator('React component generator', {
       description: 'A generator for React components',
       prompts: [
           {
               type: 'input',
               name: 'name',
               message: 'Component name',
           },
       ],
       actions: [
           {
               type: 'add',
               path: 'src/{{name}}/index.jsx',
           },
       ],
   });
```

Very intuitive IMO.
So now I have an index.jsx file residing under the `Matti` directory, which is under the `src` directory. Perfect. 

Let’s fill up the content for this file. In order to do that we’re going to use a [Handlebars](https://handlebarsjs.com/) (yes, an .hbs file, you heard right) template which will allow us to create the content according the component name we gave earlier - 

I’m creating a `plop-templates/react-component/index.hbs` under the root of the project and put the minimal code I need to create a React component:

```handlebars
import React from 'react';
import PropTypes from 'prop-types';
 
const MyComponent = (props)=> {
   return <div>MyComponent {props.sampleProp}</div>
}
 
MyComponent.propTypes = {
   sampleProp: PropTypes.number,
};
 
export default MyComponent;
```

Notice that I’m not using any dynamic fields in that template yet
Now I will tell the action previously made to use this template when creating a component:

```javascript
actions: [
           {
               type: 'add',
               path: 'src/{{name}}/index.jsx',
               templateFile: 'plop-templates/react-component/index.hbs',
           },
       ],
```

Let’s run Plop and see what we get.
Yes, the file is created with the expected content (BTW if the file already exists it will alert on that and will not override it).
It is time to use the given component name inside the template:

```handlebars
import React from 'react';
import PropTypes from 'prop-types';
 
const {{name}} = (props)=> {
   return <div>{{name}} {props.sampleProp}</div>
}
 
{{name}}.propTypes = {
   sampleProp: PropTypes.number,
};
 
export default {{name}};
```

Much better! After generating the component now the `index.jsx` file is set correctly.

Checking real fast what we have so far - A Component folder with the name given by the developer and an index.jsx file for the component. Great, moving on to creating this `index.css` file for this component

I would like the `index.css` file to contain the class name according to the given component’s name, but in lowercase and for that it would be nice to introduce a helper function which Handlebars can use. 
In the `plopfile.js` I add this:

```javascript
// Helpers
   plop.setHelper('toLowerCase', function (text) {
       return text.toLowerCase();
   });
```

Now I will create the `index.css` template under `plop-templates/react-component/index.css.hbs` with the following content:

```handlebars
.{{toLowerCase name}} {
   color: aqua;
}
```

Having the `.css` template I would like the plop action to generate it along with the `index.jsx` file. How do I do that?

This requires us to use another Plop api called “addMany” and to change the `index.jsx` template names a bit. Let’s start first with changing the template name and you will soon understand why - 

`index.hbs` is renamed to `index.jsx.hbs`

Going to the plopfile, let’s use the “addMany” action like this:

```javascript
actions: [
           {
               type: 'addMany',
               destination: 'src/{{name}}',
               base: `plop-templates/react-component/`,
               templateFiles: 'plop-templates/react-component/*',
           },
       ],
```

You might be wondering what’s going on here (and the [docs](https://plopjs.com/documentation/#addmany) are not really clear on that)
The “destination” is where we want all our files to be generated.
The “templateFiles” (note the little “s” at the end) is where all the templates for this action reside. It was a good thing there to create a directory for each generator.
The “base” is the part we would like to remove from the final file names.

Plop knows to remove the “.hbs” and the base from the final file name and this is why we changed the name of the index file. 

Let’s do some little modifications to the `index.js.hbs` template so that our component will import and use the `index.css` generated for it:

```handlebars
import React from 'react';
import PropTypes from 'prop-types';
import './index.css';
 
const {{name}} = (props)=> {
   return <div className="{{toLowerCase name}}">{{name}} {props.sampleProp}</div>
}
 
{{name}}.propTypes = {
   sampleProp: PropTypes.number,
};
 
export default {{name}};
```

Awesome! We have a generated css file with the component imports and uses. Moving on to the test file. 
We would like to create a Jest test file which will test the minimal component rendering.

We start by creating a `index.test.hbs` template at the same location of the rest of the templates:

```handlebars
import React from 'react';
import {render, screen} from '@testing-library/react';
import {{name}} from '.';
 
describe('{{name}} component', () => {
   it('should render', () => {
       const mockSampleProp = 5;
       const textQuery = `{{name}} ${mockSampleProp}`
      
       render(<{{name}} sampleProp={mockSampleProp}/>);
 
       expect(screen.getByText(textQuery)).toBeInTheDocument();
   });
});
```

We do not need to change anything in our plopfile. This template will be detected and the test file will be generated.
Running Jest to make sure it all passes and indeed - we have success :)

Moving on to the Storybook story, well… you get the drill of it. I’m creating a templated called `index.stories.jsx.hbs` with the following content:

```handlebars
import React from 'react';
import {{name}} from './index.jsx';
 
// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
export default {
 title: 'Components/{{name}}',
 component: {{name}},
 // More on argTypes: https://storybook.js.org/docs/react/api/argtypes
 argTypes: {},
};
 
// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
const Template = (args) => <div><{{name}} {...args} /></div>;
 
export const Simple = Template.bind({});
// More on args: https://storybook.js.org/docs/react/writing-stories/args
Simple.args = {
   sampleProp:5,
};
```

Running plop again and the story file is created and displayed on Story book -

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/90c1rm8wy0gs6cjwz6xi.png) 

What a marvelous component!

And that, my friends, is it :)

## To sum up…
We now have a React component generator which creates 4 files for us with the given component’s name. Any developer can now create a component and get started in a matter of seconds. 
Aside from speeding up the component creation process and allowing better DX, this helps to align the components standards in a large organization.
The code can be found on my Pedalboard monorepo code.

As always if you have any question or suggestions on how this can be done better, be sure to leave them in the comments below :)

*Hey! If you liked what you've just read check out <a href="https://twitter.com/mattibarzeev?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-show-count="false">@mattibarzeev</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> on Twitter* :beers:

<small><small><small>Photo by <a href="https://unsplash.com/@kobuagency?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">KOBU Agency</a> on <a href="https://unsplash.com/s/photos/creator?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></small></small></small>