# Templating in React Native.

Templating is a great option when you are work on a big project with a good number of teammates. It saves you a lot of time by keeping a fixed structure for your application folders and when someone new hops in to the project, it does not suck his blood trying to understand the application structure. And if you are someone, as lazy as me, it saves your monotonous task of creating files for every screen or component you create.


So, what are we going to do? Well, we are going to create templates for our App. So let's get started by creating a project first.

```shell
npx react-native --ignore-existing init MyApp --template react-native-template-typescript

```

If you wanna use javascript in your app, you can still follow along. Just the file extension has to **.js and .jsx** depending on the use case in stead of **.ts and .tsx**, rest everything is goanna be same. So, no worries. ✌

So, run the above command, the react native should give you the boiler plate code. Before, we continue installing another dependency which will be the superhero of our use-case, let's tweak a little bit the boiler plate code. 


- Create a **src** folder in the root of your project. And create two folders inside it, **app** and **generators**.
- Now move your **App.tsx** inside src/app. And create other folders as given in the diagram below.

![blog.drawio (1).png](https://cdn.hashnode.com/res/hashnode/image/upload/v1643812222575/M-XwoYjTn.png)

Why so many folders, maaan? 🤯  


![irritated GIF](https://media.giphy.com/media/300Xuw703O44gMURsS/giphy-downsized.gif)



Let me explain you.

The components directory will hold all your global components, screen will hold all your screens and the redux directory will have your **rootReducer**, **rootSaga**, and your **store**. In this way, it becomes very obvious of what each directory contains and when you are looking for something specific, you, now know where to find that. 🤩

![okay GIF](https://media.giphy.com/media/3ocrtl1PeOnK79ud78/giphy-downsized.gif)

Ok. One last pre-requisite step. I, promise, this is the last. Create an **index.tsx** file inside your components directory and add the following lines.

![punch GIF](https://media.giphy.com/media/1Qb1OMi0vpEOhv3uHb/giphy.gif)

```
/* COMPONENT IMPORTS */

export { /* COMPONENT EXPORTS */ };
```

You will very soon know the reason for adding this.

Now, it's time to onboard our super hero "Plop.js". It's basically a templating framework built on top od Handlebars.js to give you the ultimate power of templating. You can read more about it [here](https://plopjs.com/).

```shell
yarn add --dev plop
```

Now let's move into the **generators** directory.


- Create an **index.js** inside it and add the following lines.

```
const componentGenerator = require('./component');
const screenGenerator = require('./container');

module.exports = plop => {
  plop.setGenerator('component', componentGenerator);
  plop.setGenerator('screen', screenGenerator);
};

/* You may add multiple generators based on your requirements. */
```

- Now let's create these generators one by one.

- Component Generator:

1. Create a folder called **component**.
2. Add an **index.js**, an **index.tsx.hbs** and a **styles.tsx.hbs** file inside it.
3. And add the following:

   index.js :


 ```
/* eslint-disable quotes */
const componentExists = require('../checks');

module.exports = {
  description: 'Creates a Global Component',
  prompts: [
    {
      type: 'input',
      name: 'name',
      message: 'Name of the component: ',
      default: 'Button',
      validate: name => {
        if (/.+/.test(name)) {
          return componentExists(name)
            ? 'A component or container with this name already exists'
            : true;
        }

        return 'The name is required';
      },
    },
  ],
  actions: [
    {
      type: 'add',
      path: 'your_path_to_components/{{pascalCase name}}/index.tsx',
      templateFile: './component/index.tsx.hbs',
    },
    {
      type: 'add',
      path: 'your_path_to_components/{{pascalCase name}}/styles.tsx',
      templateFile: './component/styles.tsx.hbs',
    },
    {
      type: 'append',
      path: 'your_path_to_components/index.ts',
      pattern: /(\/\/ COMPONENT IMPORTS)/g,
      template: `import {{pascalCase name}} from './{{pascalCase name}}';`,
    },
    {
      type: 'append',
      path: 'your_path_to_components/index.ts',
      pattern: /(\/\/ COMPONENT EXPORTS)/g,
      template: `  {{pascalCase name}},`,
    },
  ],
};

```


index.tsx.hbs:


```
import React, {PureComponent} from "react";
import {View, Text} from "react-native";
import styles from "./styles";

export interface Props {}

export interface State {}

class {{pascalCase name}} extends PureComponent<Props, State> {
    render() {
        return (
            <View style={styles.container}>
                <Text>
                    {{pascalCase name}}
                </Text>
            </View>
        )
    }
}

export default {{pascalCase name}};
```

styles.tsx.hbs:
```
import {StyleSheet, ViewStyle} from 'react-native';

interface Styles {
  container: ViewStyle;
}

const styles = StyleSheet.create<Styles>({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});

export default styles;
```

Now you are done with the components. Now we need to also add a checker to check if the component or file we are creating already exists or not. For that. Create a **checks** directory within generators.

And add the following line.

```
const fs = require('fs');
const path = require('path');

const appComponents = fs.readdirSync(
  path.join(__dirname, '../../../app/components'),
);
const appContainers = fs.readdirSync(
  path.join(__dirname, '../../../app/screens'),
);

const components = appComponents.concat(appContainers);

function componentExists(comp) {
  return components.indexOf(comp) >= 0;
}

module.exports = componentExists;
``` 


- Now let's create the screen generators. Follow the similar steps as of components.
1.  Create a container folder. And add a index.js file. Additionally, add class.tsx.hbs, function.tsx.hbs, styles.tsx.hbs, actions.ts.hbs, reducer.ts.hbs, saga.ts.hbs, action.types.ts.hbs .
2.  Now add the following code.

index.js:

```
const componentExists = require('../checks');

module.exports = {
  description: 'Creates an App Screen',
  prompts: [
    {
      name: 'name',
      message: 'Name of the app screen: ',
      default: 'HomeScreen',
      validate: name => {
        if (/.+/.test(name)) {
          return componentExists(name)
            ? 'A component or container with this name already exists'
            : true;
        }

        return 'The name is required';
      },
    },
    {
      type: 'list',
      name: 'input',
      message: 'Choose component type: ',
      choices: ['class', 'function'],
      default: 'class',
    },
    {
      type: 'confirm',
      name: 'wantActionsAndReducer',
      default: true,
      message:
        'Do you want an actions/constants/selectors/reducer tuple for this container?',
    },
    {
      type: 'confirm',
      name: 'wantSaga',
      default: true,
      message: 'Do you want sagas for asynchronous flows? (e.g. fetching data)',
    },
  ],
  actions: res => {
    const actions = [
      {
        type: 'add',
        path: '../../app/screens/{{pascalCase name}}/styles.tsx',
        templateFile: './container/styles.tsx.hbs',
        abortOnFail: true,
      },
    ];
    if (res.input === 'function') {
      actions.push({
        type: 'add',
        path: '../../app/screens/{{pascalCase name}}/index.tsx',
        templateFile: './container/function.tsx.hbs',
        abortOnFail: true,
      });
    }

    if (res.input === 'class') {
      actions.push({
        type: 'add',
        path: '../../app/screens/{{pascalCase name}}/index.tsx',
        templateFile: './container/class.tsx.hbs',
        abortOnFail: true,
      });
    }

    if (res.wantActionsAndReducer) {
      actions.push({
        type: 'add',
        path: '../../app/screens/{{pascalCase name}}/actions.ts',
        templateFile: './container/actions.ts.hbs',
        abortOnFail: true,
      });

      actions.push({
        type: 'add',
        path: '../../app/screens/{{pascalCase name}}/action.types.ts',
        templateFile: './container/action.types.ts.hbs',
        abortOnFail: true,
      });

      actions.push({
        type: 'add',
        path: '../../app/screens/{{pascalCase name}}/type.d.ts',
        templateFile: './container/type.d.ts.hbs',
        abortOnFail: true,
      });

      actions.push({
        type: 'add',
        path: '../../app/screens/{{pascalCase name}}/selectors.ts',
        templateFile: './container/selectors.ts.hbs',
        abortOnFail: true,
      });

      actions.push({
        type: 'add',
        path: '../../app/screens/{{pascalCase name}}/reducer.ts',
        templateFile: './container/reducer.ts.hbs',
        abortOnFail: true,
      });
    }
    if (res.wantSaga) {
      actions.push({
        type: 'add',
        path: '../../app/screens/{{pascalCase name}}/saga.ts',
        templateFile: './container/saga.ts.hbs',
        abortOnFail: true,
      });
    }
    return actions;
  },
};
```

action.types.ts.hbs:
```
/*
 *
 * {{ properCase name }} action types
 *
 */

const {{ properCase name }}ActionTypes = {};

export default {{ properCase name }}ActionTypes;
```

action.ts.hbs:

```
/*
 *
 * {{ properCase name }} actions
 *
 */

 import * as actionTypes from "./action.types";
```

class.tsx.hbs:
```
import React, {PureComponent} from "react";
import {View, Text} from "react-native";
import styles from "./styles";


interface Props {}

interface State {}

class {{pascalCase name}} extends PureComponent<Props,State> {
    render() {
        return(
            <View style={styles.container}>
                <Text>{{pascalCase name}}</Text>
            </View>
        )
    }
}

export default {{pascalCase name}};
```
function.tsx.hbs:
```
import React, {memo, FC} from "react";
import {View, Text} from "react-native";
import styles from "./styles";


interface Props {}

const {{pascalCase name}}: FC<Props> = (props) => {
 return (
     <View style={styles.container}>
         <Text>{{pascalCase name}}</Text>
     </View>
 )
};

export default memo({{pascalCase name}});
```

reducer.ts.hbs:

```
import * as actionTypes from "./action.types";

/* Mention the types */
export const INITIAL_STATE = {};

/* Mention the types */
const {{ camelCase name }}Reducer = (state = INITIAL_STATE, action) => {
    switch (action.type) {
        default: 
            return state;
    }
 };

 export default {{ camelCase name }}Reducer;
```

saga.ts.hbs:

```
/*
 Add your Saga to the root saga
*/

import { take, call, put, select, all } from 'redux-saga/effects';
// import axios from "../../config/axios.config";


export function* {{ camelCase name }}Saga() {
  yield all([
    // call all other sagas here.
  ]);
}

```

selectors.ts.hbs:

```
// {{properCase name }} selectors
```

styles.tsx.hbs:

```
import {StyleSheet, ViewStyle} from 'react-native';

interface Styles {
  container: ViewStyle;
}

const styles = StyleSheet.create<Styles>({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});

export default styles;
```

types.d.ts:

```
/*
 *
 * {{ properCase name }} types
 *
 */
```

Now, you are done with your generators. Let's add it to your package.json, so that you can simply execute without having to write the complete script. 

Add this script to your **package.json**:

```
"generate": "plop --plopfile src/generators/index.js"

```

Now, just run ```
yarn generate
``` or ```
npm run generate
``` to get the CLI options and you are good to go.


See ya'. Happy Coding. Any doubts, reach out to me. :)

Stay Safe, put on a **Mask** and **Sanitize** yourself as much as you can! 😊

Like what you read? Support me by making a small donation [here](https://www.buymeacoffee.com/rofupeco). 😇

![Coding GIF](https://media.giphy.com/media/MdA16VIoXKKxNE8Stk/giphy-downsized.gif)




    





