VS Code + React + Typescript code quality setup 2020
Tags: javascript, react, vscode, typescript
Published: 2020-05-03 00:00:00

---
author: Juraj Slavicek
date: '2020-05-03'
title: VS Code + React + Typescript code quality setup 2020
tags: ['javascript', 'react', 'vscode', 'typescript']
published: true
---

So here's the thing.
I started several projects combining React and Typescript lately and found myself repeating same setup over and over again.
Usually on project's first day i would do only this chore and esentially waste one day.

Dont get me wrong create-react-app offers nice start but gives you almost nothing in terms of code quality.

As my teams usually consist of non trivial percentage of junior developers i want to make sure common mistakes are caught early, code is formatted well and commit messages make sense.

If you are experiencing same problems keep reading
We will be using `yarn` as our package manager throughout this post.
If you dont have it installed yet do it via `npm install -g yarn` in your terminal

## 1. Lets start with creating our project

```
npx create-react-app dev-experience-boilerplate --template typescript
```

We are using `npx` which is npm package runner and executes `create-react-app` package without installing it globally

Above code is equivalent to

```
npm install -g create-react-app
create-react-app dev-experience-boilerplate --template typescript
```

## 3. Linting (Eslint)

Linting is by [definition](<https://en.wikipedia.org/wiki/Lint_(software)>) tool that analyzes source code to flag programming errors, bugs, stylistic errors, and suspicious constructs. We will be using [eslint](https://eslint.org/) - linting tool for javascript.

First lets integrate eslint in our IDE by installing VS Code extension from [marketplace](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)

_In Next step will be installling all needed dependencies_

```
yarn add eslint eslint-config-airbnb eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react eslint-plugin-react-hooks
@typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-header eslint-plugin-import eslint-config-prettier --dev
```

That is lot of dependencies.
What we did here? Well we installed bunch of plugins
Lets look at them one by one

- `eslint` - Tool itself
- `eslint-config-airbnb` - Good guys in airbnb made their eslint configuration public. Everyone can use extend and override defined rules
- `eslint-plugin-react` - React specific linting rules for ESLint
- `eslint-plugin-jsx-a11y` - Set of accessibility rules on JSX elements. We want to be proper developers and deliver best possible experience also for impaired visitors of our application. One of such rules can be that `<img>` tags should have `alt` attribute so screen reader knows what is on image. If you forget to add alt wslint will yell at you
- `eslint-plugin-react-hooks` - Since react version 16.8.0 we are writing majority of our components in hooks. We want them write right.
- `@typescript-eslint/parser` - Sice our project uses typescript we need to tell eslint that our code is not vanilla javascript and needs to be parsed
- `@typescript-eslint/eslint-plugin` - Set of rules for typescript
- `eslint-config-prettier` - Eslint plugin that removes all rules that can possibly conflict with `prettier` which we will install in next step
- `eslint-plugin-header` - EsLint plugin to ensure that files begin with given comment. I personally like when every file starts with header with basic info like Author and Date. When you work in larger team its a nice way to see ownership of file and when something is not clear or right you know who you should bother with questions
- `eslint-plugin-import` - Linting of ES6 import/export syntax

Now when everything is installed lets define our rules

This is very opinionated but heres what works for me.

In root of your project create file named `.eslintrc` and paste following code snippet inside

```
{
  "parser": "@typescript-eslint/parser",
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended",
    "react-app",
    "prettier",
    "prettier/@typescript-eslint"
  ],
  "plugins": ["@typescript-eslint", "react-hooks", "header"],
  "settings": {
    "react": {
      "version": "detect"
    }
  },
  "rules": {
    "header/header": [2, "block", [{ "pattern": " \\* Author : .*" }]],
    "@typescript-eslint/consistent-type-definitions": ["warn", "type"],
    "@typescript-eslint/explicit-function-return-type": "off",
    "@typescript-eslint/explicit-member-accessibility": "off",
    "@typescript-eslint/no-angle-bracket-type-assertion": "off",
    "@typescript-eslint/no-non-null-assertion": "off",
    "@typescript-eslint/no-unused-vars": [
      "error",
      {
        "argsIgnorePattern": "^_",
        "ignoreRestSiblings": true
      }
    ],
    "@typescript-eslint/no-use-before-define": [
      "warn",
      {
        "functions": false,
        "classes": false,
        "variables": true
      }
    ],
    "import/no-extraneous-dependencies": "warn",
    "import/order": [
      "warn",
      {
        "newlines-between": "always"
      }
    ],
    "no-case-declarations": "warn",
    "no-console": "warn",
    "no-debugger": "warn",
    "no-else-return": "warn",
    "no-param-reassign": "warn",
    "no-undef": "off",
    "no-unused-vars": "off",
    "no-var": "warn",
    "object-shorthand": "warn",
    "padding-line-between-statements": [
      "warn",
      {
        "blankLine": "always",
        "prev": "*",
        "next": "class"
      },
      {
        "blankLine": "always",
        "prev": "*",
        "next": "for"
      },
      {
        "blankLine": "always",
        "prev": "*",
        "next": "function"
      },
      {
        "blankLine": "always",
        "prev": "*",
        "next": "if"
      },
      {
        "blankLine": "always",
        "prev": "*",
        "next": "return"
      },
      {
        "blankLine": "always",
        "prev": "*",
        "next": "switch"
      },
      {
        "blankLine": "always",
        "prev": "*",
        "next": "try"
      },
      {
        "blankLine": "always",
        "prev": "*",
        "next": "while"
      },
      {
        "blankLine": "always",
        "prev": "block-like",
        "next": ["let", "const"]
      }
    ],
    "prefer-const": "warn",
    "react/jsx-boolean-value": "warn",
    "react/jsx-curly-brace-presence": "warn",
    "react/jsx-key": "warn",
    "react/jsx-sort-props": [
      "warn",
      {
        "callbacksLast": true,
        "reservedFirst": true,
        "shorthandLast": true
      }
    ],
    "react/no-array-index-key": "warn",
    "react/prefer-stateless-function": "warn",
    "react/self-closing-comp": "warn",
    "react-hooks/rules-of-hooks": "error",
    "react-hooks/exhaustive-deps": "off",
    "yoda": "warn"
  }
}

```

I dont want to go into much details here but i encourage you to sit with your team go through all of them, and discuss what works and what doesn't for you. There is no single right answer to how `.eslintrc` should look like

Last thing we need to do is to set up linting command in `package.json`

Into section `scripts` add following snippet

```
 "lint": "eslint \"src/**/*.{ts,tsx}\"",
 "lint:fix": "eslint --fix \"src/**/*.{ts,tsx}\""
```

Now when you run `yarn lint` in project root
You should see output similar to this
![alt text](https://i.ibb.co/k383rnP/2020-05-03-17-56-25.png 'Yarn lint output with 14 errors/warnings')

Ok so we have 14 warnings. Lets try to fix them by running `yarn lint:fix` in project root
![alt text](https://i.ibb.co/PhWJwjy/2020-05-03-17-59-29.png" 'Yarn lint output with some of previous errors autofixed')

Awesome down to 6 with no effort. Eslint sorted props added blank lines for better readability and more for us for free.

There are some `console.log` statements in `serviceWorker.ts`
For some reason i want to leave service worker as is and not to lint this partiular file.
Eslint comes with solution for that.

Lets create `.eslintignore` file in project root and add following content inside

```
src/serviceWorker.ts
```

Now after running `yarn lint` there should be no errors. Life is beautiful again :unicorn:

## 2. Prettier

Prettier is code formatter that supports number of languages and can be easily integrated into VS Code.

Similar to eslint we first need to install VS code [extension](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)

_Add dependencies_

```
yarn add prettier --dev
```

_And create configuration file_

Lets create file `.prettierrc` in project root and paste following content inside

```
{
  "singleQuote": true,
  "trailingComma": "all",
  "printWidth": 100
}

```

Thats everything for prettier now your code will look nice and consistent across all files

If for some reason you dont want to prettify some of your files you can create `.prettierignore` file in your project root

## 3. Precommit hook

Now. You can run eslint and prettify every time you are about to commit your changes but lets be honest . We all forget.
You cannot forgot if husky barks at you though.
[Husky](https://github.com/typicode/husky) is handy tool that prevents you from accidentaly pushing changes that are well.... not ideal into repository.

Lets see it in action.

_First install dependencies_

```
yarn add husky lint-staged --dev
```

_Add following into your `package.json`'s script section_

```
"precommit": "lint-staged"
```

_And following to the **end** of `package.json`_

```
"husky": {
    "hooks": {
        "pre-commit": "lint-staged"
    }
},
"lint-staged": {
    "src/**/*.{js,ts,tsx}": [
      "prettier --config .prettierrc --write",
      "eslint --fix \"src/**/*.{ts,tsx}\"",
      "eslint \"src/**/*.{ts,tsx}\"",
      "git add"
    ]
  }
```

To see if our setup works lets create unused variable in `App.tsx`. And try to commit our changes via `git add .` and `git commit -m "This shouldnt work"`
![alt text](https://i.ibb.co/NVvfhrB/2020-05-03-18-33-00.png 'Git command blocked by husky due to failing eslint')

And indeed husky barked and we have to fix our code in order to be able to push it into repository.

## 4. Commit message hook

Last thing i want to cover is consistent naming of commit messages. This is common mistake in lots of repositories. You can of course create your own git hook but i recently fell in love with [git-cz](https://www.npmjs.com/package/git-cz) which is tool for interactively commiting changes via your favourite terminal.

_Installation_

```
yarn add git-cz @commitlint/cli @commitlint/config-conventional --dev
```

_Add following into your `package.json`'s script section_

```
 "commit": "clear && git-cz"
```

_Add following to the **end** of `package.json`_

```
 "commitlint": {
    "extends": [
      "@commitlint/config-conventional"
    ]
  }
```

And last thing is to tell husky to run our new commit-msg hook
We do this by changing husky section in `package.json`

```
"husky": {
    "hooks": {
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
      "pre-commit": "lint-staged"
    }
  },
```

We can test our new git hook by running `yarn commit`
![alt text](https://i.ibb.co/YZ8zphx/2020-05-03-18-58-17.png 'Git command blocked by husky due to failing eslint')

You can see this awesome cli tool that lets you choose type of change you are about to commit and more. This can be all configured
In default config you will fill in folllwing:

- Type of change (test, feature, fix, chore, docs, refactor, style, ci/cd and performance)
- Commit message
- Longer description (optional)
- List of breaking changes (optional)
- Referenced issue (i.e JIRA task number)

And commit messages are now consistent across whole team
Plus you get neat commit message icons like this
![alt text](https://i.ibb.co/LQzN5BC/2020-05-03-19-03-10.png 'Git command blocked by husky due to failing eslint')

You can find whole working solution [on github](https://github.com/Ziker22/code-quality-boilerplate)
If you liked this article you can follow me [on twitter](https://twitter.com/ziker22)