Setting up ESLint and Prettier using lint-Staged and Husky

For NextJS and React

Setting up ESLint and Prettier using lint-Staged and Husky

Initial setup

Setting up ESLint

only one command is now needed to install and setup ESLint. In your terminal or PowerShell run:

npm init @eslint/config

Terminal showing the install and initialisation of ESLint

this should add a file called .eslintrc.{js,yml,json} with the basic config of

{
    "env": {
        "browser": true,
        "es2021": true
    },
    "extends": [
        "standard-with-typescript",
        "plugin:react/recommended",
        "next/core-web-vitals"
    ],
    "parserOptions": {
        "ecmaVersion": "latest",
        "sourceType": "module"
    },
    "plugins": [
        "react"
    ],
    "rules": {
    }
}

Setting up Prettier

npm install --save-dev --save-exact prettier
node --eval "fs.writeFileSync('.prettierrc','{}\n')"

Terminal output showing the installation and setup of prettier

add a new file in the root of your project called .prettierignore now add and folders or files you don't want prettier to format

build
coverage
public

prettier configuration

{
    "trailingComma": "es5",
    "tabWidth": 4,
    "semi": true,
    "singleQuote": true
}

Lets run through these settings in more detail

trailingComma

the options are all, es5 or none The es5 option will add a trailing comma where it is valid in ES5 for objects, arrays, etc... The default is all

tabWidth

how many spaces you want a tab to equal. I like 4 as it make the indents more noticeable and easier for me to read. The default is 2

semi

If true then it will add a semicolon to the end of every statement. When I first started to code I was bad at adding these. Now after coding with this enabled for a while I still miss the occasional one but I pick up in it when looking at someone else's code. The default is true`

singleQuotes

This is to use single quotes instead of double quotes, while this is a preference it is good to state the preference so others know what style is prefered. The default is false

now run the following command to format all your files

npx prettier . --write

Terminal out put of running prettier to enforce the configuration

ESLint configuration

Install plugins

To start with we are going to extend some other ESLint plugins:

npm install @typescript-eslint/eslint-plugin eslint-plugin-jsx-a11y eslint-config-prettier eslint-plugin-import eslint-config-airbnb eslint-config-airbnb-typescript --save-dev

eslint-plugin-jsx-a11y - checks to make sure your code follows the accessibility rules eslint-config-prettier - Turns off all rules that are unnecessary or might conflict with Prettier eslint-plugin-import - This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, and prevent issues with misspelling of file paths and import names eslint-config-airbnb - This style guide is mostly based on the standards that are currently prevalent in JavaScript

Configuring the .eslint.json file

Adding in the plugins we just installed

In the .eslint.json file we will stat by extending the plugins we have already installed.

 "extends": [
        "plugin:@typescript-eslint/recommended",
        "plugin:jsx-a11y/recommended",
        "airbnb",
        "airbnb-typescript",
        "prettier"
    ],

react-in-jsx-scope

The new version of React doesn't require you to add import React from "react" if you are not using it so adding this allows you to remove that import

"react/react-in-jsx-scope": [
            "off"
        ],

@typescript-eslint/indent

Displays a warning if the correct indentation is not used, this is also linked to the prettier configuration so make sure these match

"@typescript-eslint/indent": [
            "warn",
            4
        ],

import/extensions

If you import .js/.jsx/.ts/.tsx files then you don't need to add the extension and it works fine. This is used so that there is consistency on file imports across your project. This would display an error if you never added extensions for scss, svg or JSON files.

        "import/extensions": [
            "error",
            "never",
            {
                "scss": "always",
                "svg": "always",
                "json": "always"
            }
        ],

max-len

If you don't want long lines of code as they can be harder to read, then add the following rule. In this rule we are limiting the line to 120 characters but this can be adjusted to your liking, but we are going to ignore this rule if it is a URL or regex pattern

        "max-len": ["error", 
            {
                "code": 120,
                "ignoreUrls": true,
                "ignoreRegExpLiterals": true

         }],

no-console

The no console rule is very good at catching console logs left behind from debugging. This can help stop any unintentional console.log statement from making it into production. This rule also has an exception for console.error so you can send any error messages to the console if needed

"no-console": [
            "error",
            {
                "allow": [
                    "error"
                ]
            }
        ],

comma-dangle & @typescript-eslint/comma-dangle

The next two are for the same thing but from different packages. They warn if a trailing coma is not added to the end of every line but only if they are spread over multiple lines and are not need if it a single line

        "comma-dangle": [
            "warn",
            {
                "arrays": "always-multiline",
                "objects": "always-multiline",
                "imports": "always-multiline",
                "exports": "always-multiline",
                "functions": "never"
            }
        ],
        "@typescript-eslint/comma-dangle": [
            "warn",
            {
                "arrays": "always-multiline",
                "objects": "always-multiline",
                "imports": "always-multiline",
                "exports": "always-multiline",
                "functions": "never"
            }
        ],

react/function-component-definition

Everyone has their favourite way to create a functional component but if you want to make sure they are all created the same then you can add this to your file, and this will force all the files to use arrow functions but options for the other methods are available.

"react/function-component-definition": [
            "error",
            {
                "namedComponents": "arrow-function",
                "unnamedComponents": "arrow-function"
            }
        ]

react/require-default-props

If some of your props could be undefended then you will get an error asking for you to create defaultProps for it. DefaultProps are being going to be removed from a future release of JavaScript so it is best not to use them so you can turn them off by adding

"react/require-default-props": "off",

react/display-name

By default, React want you to set a display name for you function or class but this is usually for debugging and is usually inferred by the name of the function or class. So to stop you getting this error you can tunr it off by adding this rule:

"react/display-name": "off"

@typescript-eslint/explicit-function-return-type

By default, TypeScript wants you to declare the return type of each function even the pages so if you want to disable that then you can add

"@typescript-eslint/explicit-function-return-type": "off"

Final .eslint.json file

If you have added al these rules then your .eslint.json should look like this

{
    "env": {
        "browser": true,
        "es2021": true
    },
    "extends": [
        "standard-with-typescript",
        "plugin:react/recommended",
        "next/core-web-vitals",
        "plugin:@typescript-eslint/recommended",
        "plugin:jsx-a11y/recommended",
        "airbnb",
        "airbnb-typescript",
        "prettier"
    ],
    "parserOptions": {
        "ecmaVersion": "latest",
        "sourceType": "module"
    },
    "plugins": [
        "react"
    ],
    "rules": {
        "react/react-in-jsx-scope": [
            "off"
        ],
        "@typescript-eslint/indent": [
            "warn",
            4
        ],
        "import/extensions": [
            "error",
            "never",
             {
                "scss": "always",
                 "svg": "always",
                  "json": "always"
              }
        ],
        "max-len": ["error", 
        {
          "code": 120,
          "ignoreUrls": true,
          "ignoreRegExpLiterals": true  
         }],
     "no-console": [
          "error",
          {
              "allow": [
                  "error"
              ]
          }
      ],
      "comma-dangle": [
        "warn",
        {
            "arrays": "always-multiline",
            "objects": "always-multiline",
            "imports": "always-multiline",
            "exports": "always-multiline",
            "functions": "never"
        }
    ],
    "@typescript-eslint/comma-dangle": [
        "warn",
        {
            "arrays": "always-multiline",
            "objects": "always-multiline",
            "imports": "always-multiline",
            "exports": "always-multiline",
            "functions": "never"
        }
    ],
    "@typescript-eslint/no-unused-vars": "off",
      "@typescript-eslint/no-unused-vars-experimental": "off",
      "@typescript-eslint/naming-convention": "off",
      "react/display-name": "off",
      "react/require-default-props": "off",
      "react/function-component-definition": [
          "error",
          {
              "namedComponents": "arrow-function",
              "unnamedComponents": "arrow-function"
          }
      ],
      "@typescript-eslint/explicit-function-return-type": "off"     
    }
}

Examples of errors

I am using the VS code extension error lens to show the errors on screen but here are what some of them will look like

VS Code showing ESLint errors

VS Code showing more ESLint errors

Setting up Husky and lint-staged

Now that we have ESLint and prettier working with VS Code and in our project, it is time to make sure that these standards are kept especially if someone is using a different IDE or if they don't have the prettier or ESLint extensions installed.

This is where Husky and lint staged come in. When you commit your changes husky kicks in and runs any scripts you want, in this stage it is lint-staged. Lint-staged will run all the formatters on your staged files only. This makes sure that any files match your standards before they are committed but you will also need to make sure all your existing files are fixed before you implement this.

This is the order in which things happen

  1. You commit your changes git commit -m <Message>

  2. This invokes husky which runs the pre commit script which in this case is lint-staged

  3. Lint-staged will run ESLint and prettier it will try to fix the code

  4. Your changes are committed if it could fix everything otherwise it fails and nothing is committed

Install and Setup

To install both packaged simple run:

npm i -D husky lint-staged

Now we need to initialise husky by running:

npx husky-init

This creates a few files and folders in your project

now we start by adding a prepare script to the package.json file

npm pkg set scripts.prepare="husky install"
npm run prepare

Currently you husky pre-commit file only has the following code in it

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npm test

So now we need to add our lint staged command so that it will run and we do this by typing the following command in your terminal

npx husky add .husky/pre-commit "npx lint-staged"

As we don't have any tests set up yet you need to remove that line so now your pre-commit file looks like

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx lint-staged

You could always have just added the line instead of the above command as well 😉

Now that all the husky setup is done we need to finish adding the script for lint-staged into a new file called .lintstagedrc.json in the root of your project to this we add:

{
"**/*.{js, jsx,ts,tsx}": [
    "npm run lint src"
  ],
  "**/*.{json,js,ts,jsx,tsx,html}": [
    "npx prettier"
  ]
}

in the above code this will not fix any issues but will instead report any issues it finds and if it finds any it will fail the commit.

If we try making a small edit to the testing file and then try to commit this file it will throw the following error

./app/components/testing.tsx
Error: This rule requires the `strictNullChecks` compiler option to be turned on to function correctly.  @typescript-eslint/prefer-nullish-coalescing
Error: This rule requires the `strictNullChecks` compiler option to be turned on to function correctly.  @typescript-eslint/strict-boolean-expressions
5:1  Warning: Expected indentation of 4 spaces but found 2.  @typescript-eslint/indent
9:10  Warning: Missing trailing comma.  comma-dangle
9:10  Warning: Missing trailing comma.  @typescript-eslint/comma-dangle
10:1  Warning: Expected indentation of 4 spaces but found 8.  @typescript-eslint/indent
13:1  Warning: Expected indentation of 4 spaces but found 8.  @typescript-eslint/indent
13:9  Error: Unexpected console statement.  no-console
15:1  Warning: Expected indentation of 4 spaces but found 2.  @typescript-eslint/indent
16:1  Warning: Expected indentation of 8 spaces but found 4.  @typescript-eslint/indent
17:1  Warning: Expected indentation of 4 spaces but found 2.  @typescript-eslint/indent

./app/layout.tsx
Error: This rule requires the `strictNullChecks` compiler option to be turned on to function correctly.  @typescript-eslint/prefer-nullish-coalescing
Error: This rule requires the `strictNullChecks` compiler option to be turned on to function correctly.  @typescript-eslint/strict-boolean-expressions
8:1  Warning: Expected indentation of 4 spaces but found 2.  @typescript-eslint/indent
9:1  Warning: Expected indentation of 4 spaces but found 2.  @typescript-eslint/indent
12:16  Error: Function component is not an arrow function  react/function-component-definition
13:1  Warning: Expected indentation of 4 spaces but found 2.  @typescript-eslint/indent
15:1  Warning: Expected indentation of 4 spaces but found 2.  @typescript-eslint/indent
17:1  Warning: Expected indentation of 4 spaces but found 2.  @typescript-eslint/indent
18:1  Warning: Expected indentation of 8 spaces but found 4.  @typescript-eslint/indent
19:1  Warning: Expected indentation of 12 spaces but found 6.  @typescript-eslint/indent
20:1  Warning: Expected indentation of 8 spaces but found 4.  @typescript-eslint/indent
21:1  Warning: Expected indentation of 4 spaces but found 2.  @typescript-eslint/indent

./app/page.tsx
Error: This rule requires the `strictNullChecks` compiler option to be turned on to function correctly.  @typescript-eslint/prefer-nullish-coalescing
Error: This rule requires the `strictNullChecks` compiler option to be turned on to function correctly.  @typescript-eslint/strict-boolean-expressions
3:16  Error: Function component is not an arrow function  react/function-component-definition
6:1  Warning: Expected indentation of 4 spaces but found 2.  @typescript-eslint/indent
11:1  Error: This line has a length of 313. Maximum allowed is 120.  max-len
15:1  Error: This line has a length of 207. Maximum allowed is 120.  max-len
35:1  Error: This line has a length of 639. Maximum allowed is 120.  max-len

At this stage you could either leave it like that so that you need to manually fix any errors that appear or you can add --fix so that it will try and fix any errors it comes across Also you can add --write to prettier so your .lintstagedrc.json will now look like

{
"**/*.{js,jsx,ts,tsx}": [
    "npm run lint src --fix"
  ],
  "**/*.{json,js,ts,jsx,tsx,html}": [
    "npx prettier --write"
  ]
}

GitHub Repo

All these files can be found on my GitHub Repo