React, Tailwind and TypeScript
Tags: tutorial, webdev, typescript, react
Published: 2021-05-19 17:24:17

Hey Dev's

Hope you enjoyed the TypeScript tutorial. Transitioning for the client project was quite a learning process. We have to evolve and allow ourselves enjoy the process of learning different technologies. Next.js and React are similar in many ways just like Nuxt and Vue.

In this tutorial we are going to learn the following:
- How to setup a React and TypeScript project
- How to add Tailwind to a React application
- How to add Cypress e2e Testing to your application
- How to configure basic routing.

To get started with React and TypeScript run the following command.

```
npx create-react-app my__app__name --template typescript

OR

yarn create react-app my__app__name --template typescript
```

This will create a React project with TypeScript configured in our directory. Now we need to make some changes in our application. Let us begin by configuring Tailwind in the application.

```javascript
cd my__app__name

// install the following packages for Tailwind
npm install -D tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9

// Then install craco package
 npm install @craco/craco

// I always install the tailwind forms packages because most applications will at some point require forms.
npm install @tailwindcss/forms

// run the following commad. This will create a file called tailwind.config.js
npx tailwind init

```

Now that we have installed all the packages and initialized Tailwind let us create a file called craco.config.js in our root folder.

```javascript
// __craco.config.js__
touch craco.config.js

// add the following content in the craco.config.js
module.exports = {
  style: {
    postcss: {
      plugins: [
        require('tailwindcss'),
        require('autoprefixer'),
      ],
    },
  },
}
```

In our tailwind.config.js update the file as follows:
```javascript
// tailwind.config.js
// in this file we can add the customized colors tailwind provides.

const colors = require('tailwindcss/colors')
module.exports = {
   purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'],
    darkMode: false, // or 'media' or 'class'
    theme: {
      extend: {},
      colors: {
      transparent: 'transparent',
      current: 'currentColor'
      ....
    }
    },
    variants: {
      extend: {},
    },
    plugins: [
      require('@tailwindcss/forms'), // import tailwind forms
   ],
  }
```

In our package.json file we need to update as follows:

```javascript
// __package.json__
  {
    // ...
    "scripts": {

     "start": "react-scripts start", // remove

     "build": "react-scripts build", // remove

     "test": "react-scripts test", // remove

     "start": "craco start", // add

     "build": "craco build", // add

     "test": "craco test", // add
      "eject": "react-scripts eject" // stays the same
    },
  }
```

The only thing remaining is to import the tailwind css in our index.css file.

```javascript
//__./src/index.css__
@tailwind base;
@tailwind components;
@tailwind utilities;
```

That is all we have to do to configure our application to use Tailwind. A bit of some cleanup to go and our application will be ready.

This is what our application structure looks like.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/owg12v44laqtua3nlfic.png)

Let us organize this application. To begin with let us start with Test Directory. These are our check and balances ensure you keep test closer when building your application.

Testing an application has a broad scope, in my opinion i find unit testing and e2e testing a greater way of starting to keep check balances to your application. React provides React Testing Library which can be used for unit testing and is installed by default while we can leverage Cypress for e2e testing.

Let us install cypress and configure it.

```javascript
npm install -D cypress
```

Okay now we have different options of structuring our application we can decouple our application and arrange with each component running its functions individually for example let us assume we have implemented chatting in our application.

We can create a directory in our pages or component directory called chatting and group all the files and test that runs chatting in this directory. This will allow us separate all the chatting files and components from other operations of the application.

For test i always prefer to group them together in one directory and separate them in unit and e2e/cypress directories. In this case create a directory called tests

```
// create a directory called tests
mkdir __tests__

// create two directories in the __tests__ directory
mkdir ./src/__tests__/units
mkdir ./src/__tests__/cypress or e2e // whichever name you decide

// In the above directories we can further subdivided and separate the tests in directories for example chatting which can hold all the tests for chatting etc
```

Next, in the cypress directory/e2e create three directories as follows and .eslintrc.js file.

```
mkdir ./src/__tests__/cypress/plugin
mkdir ./src/__tests__/cypress/specs
mkdir ./src/__tests__/cypress/support

touch .eslintrc.js
```
The specs directory will hold all our e2e test files. Create a file called Home.spec.ts and the following line.

```
// __Home.spec.ts__
export {}
// assert errors are displayed when user submit without email and code
// add id to this p tag in App.tsx
it("assert title Edit src/App.tsx and save to reload.", () => {
    cy.visit("http://localhost:3000/");  
    cy.get('#title').should('contain', 'Edit src/App.tsx and save to reload.')
});
```

In the plugin and support directories in each create a index.js files. While in support directory add a file called command.js

Let us now update each of these files:

####./src/__tests__cypress/plugin/index.js
```
// __index.js__
/* eslint-disable arrow-body-style */
// https://docs.cypress.io/guides/guides/plugins-guide.html

// if you need a custom webpack configuration you can uncomment the following import
// and then use the `file:preprocessor` event
// as explained in the cypress docs
// https://docs.cypress.io/api/plugins/preprocessors-api.html#Examples

// /* eslint-disable import/no-extraneous-dependencies, global-require */
// const webpack = require('@cypress/webpack-preprocessor')

module.exports = (on, config) => {
  // on('file:preprocessor', webpack({
  //  webpackOptions: require('@vue/cli-service/webpack.config'),
  //  watchOptions: {}
  // }))

  return Object.assign({}, config, {
    fixturesFolder: "src/__tests__/e2e/fixtures",
    integrationFolder: "src/__tests__/e2e/specs",
    screenshotsFolder: "src/__tests__/e2e/screenshots",
    videosFolder: "src/__tests__/e2e/videos",
    supportFile: "src/__tests__/e2e/support/index.js"
  });
};
```

####./src/__tests__/cypress/support/index.js
```
// __index.js__
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************

// Import commands.js using ES2015 syntax:
import "./commands";

// Alternatively you can use CommonJS syntax:
// require('./commands')
```

####./src/__tests__/cypress/support/command.js
```
// __commands.js__
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This is will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
```

####./src/__tests__/cypress/.eslintrc.js
```
// __.eslintrc.js__
module.exports = {
    plugins: ["cypress"],
    env: {
      mocha: true,
      "cypress/globals": true
    },
    rules: {
      strict: "off"
    },
};
```

Finally we need an entry point in the root directory for cypress. For this create a cypress.json file.

```javascript
// __cypress.json__
// cypress.json  
{
  "pluginsFile": "src/__tests__/e2e/plugins/index.js"
}
```

Once we have done this let us update the package.json file.

```javascript
// __package.json__
// update the scripts sections
"scripts" {
    ....
    "cy:run": "cypress run",
    "cypress:open": "cypress open"
}
```

Finally update the tsconfig.json file with the following:

```javascript
// __tsconfig.json__
// under compilerOptions
{
  "compilerOptions" {
     ....,
     "types": ["cypress"]
  }
}
```

So we now have cypress configured in our application and test units folder.

Almost done one more thing let us clean up the src folder.
- Create an assets directory
In this directory we are going to hold all our assets images and css. We will separate the two by creating two more directories called css and images.

```
mkdir ./src/assets

// create images and css files
mkdir ./src/assets/css
mkdir ./src/assets/images
```

Once done clear all .css and .png files in the src folder and bundle them in the css and images respectively.

- Create pages OR components directory. 
Some people might prefer to use modules whichever method suits you. These directories will hold all pages.

```
// create pages directory
mkdir pages

// create a Home.tsx file in this directory
touch Home.tsx
```

- Create a routes directory and in it create a Routes.tsx file. This directory will hold all our public routes. In case we need to protect some routes we can created a Protected.tsx file thereby separating our public and protected routes

```
// routes directory
mkdir routes

// routes.tsx file
touch Routes.tsx file
```

Great now let us add routes. To add the routes we will use [react router](https://reactrouter.com/web/guides/quick-start). To install let us run the following commands.

```
// install react router
npm install react-router-dom

// because we are using typescript we need to install the types
npm i --save-dev @types/react-router-dom
```
Our entry point will be index.tsx.

```javascript
//__index.tsx__
import { BrowserRouter } from "react-router-dom"

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById('root')
);
```

In the Routes.tsx update it as follows:

```javascript
// __Routes.tsx__
import { Switch, Route } from "react-router-dom"

// import the Home.tsx component
import Home from "../routes/Home"

function Routes (){
   return(
     <Switch>
        <Route path="/" exact component={Home}></Route>
     </Switch>
  )
}
```

In the App.tsx file clear everything and replace with the following:

```javascript
// __App.tsx__
// import the Routes.tsx component
import Routes from "../routes/Routes"

function Routes (){
   return(
     <div>
        <Routes />
     </div>
  )
}
```

Now run you application. Our entry point will be Home.tsx and this is the file you should start editing.

```
// run your application
yarn start

// visit your application on
https://localhost:3000
```

In case you will be using AWS Amplify to host your application update the following.

- Create amplify.yml file in your root folder

```javascript
test:
  phases:
    preTest:
      commands:
        - npm ci
        - npm install wait-on
        - npm install pm2
        - npm install mocha@5.2.0 mochawesome mochawesome-merge mochawesome-report-generator
        - npx pm2 start npm -- start
        - 'npx wait-on http://localhost:3000'
    test:
      commands:
        - 'npx cypress run --reporter mochawesome --reporter-options "reportDir=cypress/report/mochawesome-report,overwrite=false,html=false,json=true,timestamp=mmddyyyy_HHMMss"'
    postTest:
      commands:
        - npx mochawesome-merge cypress/report/mochawesome-report/mochawesome*.json > cypress/report/mochawesome.json
        - npx pm2 kill
  artifacts:
    baseDirectory: cypress
    configFilePath: '**/mochawesome.json'
    files:
      - '**/*.png'
      - '**/*.mp4'
```

To disable Cypress Testing and maintain the same environment for your application. Go to the environment variables settings in your Amplify app dashboard and add a rule.

1. variable: USER_DISABLE_TESTS
2. value: true
3. branches: All branches

One more thing in case you are getting access denied when you refresh your application in the browser. Here is a solution. Go to the redirects in the settings of your Amplify App and add a new rule.

1. Original Address: </^[^.]+$|\.(?!(css|gif|ico|jpg|js|png|txt|svg|woff|woff2|ttf|map|json)$)([^.]+$)/>
2. Destination Address: /index.html
3. Redirect Type: 200

That is all you need to do in getting started with React, Tailwind and TypeScript.

Thank you and see you in the next tutorials.