Building high-quality apps is challenging for many reasons:

  • The more your codebase grows, the more it becomes hard to maintain.
  • Developers have different ways to code, which can confuse you and slow you down.
  • JavaScript is a language that seems easy to learn at first, but it's not that easy. You can fall into common pitfalls. The same goes with React (or any other front-end library/framework).
  • Etc.

Nowadays, many tools allow us to write quality code and make sure our apps don't break. You may think of testing tools like Jest, React Testing Library or Cypress at first, but before writing tests, you can add the first layer of safety in your app with a linter.

Lint your code

A linter analyses your code statically. It means it checks out the code without executing it. But why use a linter, you might think? Because you can build an app while writing awful code. Thus, it's essential to have a tool that spots for you common errors and bad practices.

The best part of a linter is finding potential errors in your code that don't look like them. As an example, here is a for-loop that, at first glance, will log numbers from 0 to 4:

javascript for (var i = 0; i < 5; i++) { setTimeout(() => console.log(i), 1000); }

However, it logs five times 5. Weird, right? The reason behind this is that the var keyword is function-scoped. As there are no functions here, i will be a global variable. Thus, by the time the first setTimeout's callback finishes running, the loop has always been executed, and so, the last value of i is 5.

You can fix this behavior by using the let keyword, which is block-scoped:

javascript for (let i = 0; i < 5; i++) { setTimeout(() => console.log(i), 1000); }

This time, each iteration has its own i variable and is not declared globally.

It's almost impossible to guess what would happen here if you haven't run into it at least once in your developer's life. But a linter can! It would see you're using the var keyword in a for-loop. Inside this loop, you're using a function that references variables outside of its scope, which seems like a bad practice. Thus, it would throw an error at you, and you would know what to do.

ESLint

Now that you saw why linting your code is important let's discover ESLint. It's an open-source project initially created by Nicholas C. Zakas, which provides a pluggable linting utility for JavaScript.

Basically, ESLint parses your code, analyses it, and runs linting rules. These rules may trigger warnings or errors to let you know if your code is right or wrong.

For example, one popular rule is no-undef: it makes sure your code doesn't have any undeclared variables. With no-undef, the following code would be incorrect:

javascript // ❌ Oops, what is add? const result = add(1, 2);

Indeed. ESLint wouldn't know what add refers to and would throw an error at you. You need to explicitly declare add to correct it:

javascript // ✅ Correct function add() {} const result = add(1, 2);

Install ESLint

You can install ESLint in two different ways:

  1. Globally: npm install eslint -g
  2. On a project: npm install eslint --save-dev

I recommend that you install ESLint directly in a project. Indeed, if you install it globally, you could make ESLint clashing with your other projects or with other developers (and run into one these "It works on my machine!" problems).

Note: You can also use the ESLint extension for VSCode. It'll deeply improve your developer experience by highlighting errors and warnings directly in your code.

Configure ESLint

What's great with ESLint is that it's highly configurable. All you have to do is create a .eslintrc file at the root of your project, and then you can run ESLint on any files you want.

Note: it is also possible to define an ESLint configuration inside a package.json file. For that, just put your configuration object in a eslintConfig key.

This configuration file takes the form of a JSON object. You can specify many options to do so:

parserOptions

The parserOptions: tells ESLint how you want it to parse your code. Most of the time, you specify which ECMAScript version (ES6, ES2018, etc.) you are using with the ecmaVersion key. This is also where you tell ESLint if you use ECMAScript modules (import and export) by setting sourceType to module.

environment

environment defines predefined global variables you're using. For example, Jest allows you to use global variables in your test files such as describe, it or test. However, ESLint won't know what these variables refer to if you haven't explicitly told it. For that, set jest to true.

globals

You might sometimes be declaring global variables by yourself. Then, you need to add them to this object.

plugins

Basically, plugins are a set of ESLint rules related to the same subject. As an example, eslint-plugin-react contains many rules related to React.

Caution: you have to install the plugin as a dev dependency if you want your rules to work correctly.

rules

These are the linting rules we were talking about before. Each rule has a value that is either off if you want to disable the rule, warn if it should show a warning or error if it should throw an error.

extends

Allows you to extend your configuration file from other configurations. A popular one is react-app provided by Create React App. That's also in extends that you can use popular style guides such as the one of Airbnb, Google or Standard.

Inline configuration

You can modify ESLint's configuration inline with special comments. As an example, you could do the following to the incorrect code related to no-undef:

javascript // eslint-disable-next-line const result = add(1, 2);

With that, ESLint will stop complaining.

Use it with caution, though! It's not a good idea to disable ESLint every time it spots errors. It often means something's wrong with your code.

ESLint CLI

ESLint has a CLI to lint files. You can find all the options on ESLint's docs. The one you will use the most is the --fix option, which fixes the files when ESLint can. For example, the following command lints every file present in the codebase:

shell eslint .

Then you can include these scripts in your package.json file:

json { "scripts": { "lint": "eslint .", "lint:fix": "eslint --fix ." } }

You can use these scripts using the npm run command. One will just lint the codebase while the other one will lint and try to fix whatever it can fix.

ESLint Configuration examples

Here is an example of a .eslintrc file:

json { "env": { "commonjs": true, "es6": true, "node": true }, "extends": "eslint:recommended", "parserOptions": { "ecmaVersion": 2020 }, "rules": { "no-console": "error" } }

In this configuration, we:

  • Extend the eslint:recommended configuration
  • Tell ESLint we'll use CommonJS global variables (require instead of import)
  • Specify we'll write our JavaScript with the 2020 version of ECMAScript syntax
  • Disallow the use of console.log.

Here is another ESLint configuration:

json { "env": { "browser": true, "jest": true, "es6": true }, "plugins": ["import"], "extends": "eslint:recommended", "parserOptions": { "ecmaVersion": 2020, "sourceType": "module" }, "rules": { "no-console": "warn", "no-eval": "error", "import/first": "error" } }

This ESLint config meets the following requirements:

If you're curious, you can also check out the configuration of create-react-app looks like, under the hood, it's really interesting!

Prettier

You now know how to enforce some guidelines in your project and avoid common pitfalls with ESLint. Great! Now what? Formatting your code.

Imagine you work in a team of three developers on the same project:

  • One developer always uses single quotes instead of double-quotes.
  • Another one uses indentation of four spaces and makes its line very short.
  • However, you prefer indentation of two spaces, and you usually use double-quotes.

Then, what happens if you need to collaborate on the same project? Well:

  • Your code is a mess.
  • You waste your time rewriting others code with your own code style

How can you fix it? Use a common code style. How to do it? With Prettier: an opinionated code formatter. It obviously supports JavaScript but also many other languages like JSX, CSS, JSON or Vue.

Install Prettier

You can install Prettier in two different ways:

  1. Globally: npm install prettier -g
  2. On a project: npm install prettier --save-dev

Just like ESLint, I recommend that you install Prettier in a project instead of globally because it could be clashing with other projects.

Note: You can also use the Prettier extension for VSCode. It'll deeply improve your developer experience by formatting your code from VSCode.

Configure Prettier

Prettier, as said above, is an opinionated code formatter. It will format your code by default in a way that you may not like. But they allow you to modify a few things. You can configure it via a .prettierrc file or via a prettier key in your package.json file. As an example, here are some rules you can configure:

  • singleQuote: true if you want to use single quotes, false otherwise.
  • printWidth: a number that specifies the line length that the printer will wrap on.
  • semi: true if you want to print semicolons at the end of every statement, false otherwise.

Click here if you want to find more options.

Prettier CLI

Prettier, just like ESLint, has a CLI to format files. You can find many options on Prettier's docs. The one you will use the most is the --write option, which is similar to the --fix option of ESLint. For example the following command formats every .js or .jsx file located in a src folder:

shell prettier --write src/*.(js|jsx)

Then you can use this CLI option to include a format script in your package.json file:

json { "scripts": { "format": "prettier --write \"**/*.+(js|jsx|json|css|md)\"" } }

Now, you just have to run npm run format to format your entire codebase.

Integrate Prettier with ESLint

Prettier can format our code, but who said ESLint doesn't? Indeed, ESLint has formatting rules too like max-len (similar to printWidth) or quotes. Well, if you use Prettier, you aren't going to need such rules. Prettier replaces ESLint's formatting rules but doesn't replace code-quality rules.

Thus, you can integrate Prettier in your ESLint configuration by installing the following:

json npm install --save-dev eslint-config-prettier eslint-plugin-prettier

eslint-config-prettier disables rules that conflict with Prettier. At the same time eslint-plugin-prettier adds the rule that format content using Prettier. You can enable this configuration by using the recommended one:

json { "extends": ["plugin:prettier/recommended"] }

Prettier Configuration example

Here is an example of a Prettier configuration (.prettierrc):

json { "printWidth": 85, "arrowParens": "always", "semi": false, "tabWidth": 2 }

Basically, it means that Prettier:

  • Will wrap on line that exceeds 85 characters.
  • Will always use parenthesis around arrow functions, even if there is one argument.
  • Won't print semicolons at the end of every statement.
  • Will use two spaces per indentation level.

Let's take an example of a messy yet straightforward React component and visualize the effects of Prettier by trying out its online playground:

Example of Prettier formatting

That's instantly more readable, and trust me, you would have wasted your time formatting this by hand. So using a formatter is essential.

Lint and format at each commit

You can lint and run a codebase, great. But does it mean that now you should lint and format your code whenever you want? No!

If you use Git in your project - and who doesn't - you can make use of Git hooks to run ESLint and Prettier before each commit or each push. Then, your code is constantly linted and formatted before deploying to production 😉

husky

We're not talking about the dog here. We're talking about the tool that allows you to set up Git hooks very easily. Let's say your package.json has the following scripts, and you want to run lint and format at each commit:

json { "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", "lint": "eslint .", "format": "prettier --write \"**/*.+(js|jsx|json|yml|yaml|css|md|vue)\"" } }

Then, you can add Git hooks in two easy steps:

  1. Install husky as a dev dependency:

shell npm install --save-dev husky

  1. Insert the following in your package.json:

shell { "husky": { "hooks": { "pre-commit": "npm run lint && npm run format" } } }

That's all. At each commit, the command associated with pre-commit will be run. So your codebase will be linted and formatted.

pre-commit is the most used Git hook by far, but you can also use other hooks like pre-push or post-commit.

lint-staged

Bear with me. This tool will be the last to setup. The more your codebase will grow, the more linting and formatting will be longer. Imagine you have more than 1000 components. You don't want to wait for ESLint to lint all your codebase, don't you? In the end, the only files that need to be linted are the ones that just have been modified. For that, you can use lint-staged. Indeed, this tool will make sure you'll lint files that will be committed, that is to say, the ones that are staged.

To install it, you have to run the following command:

javascript npm install --save-dev lint-staged

Then, you have to either add a lint-staged key to your package.json or a .lintstagedrc file for the configuration. According to lint-staged docs, the configuration should be an object where each value is one or more commands to run, and its key is a glob pattern to use for these commands. Here is an example:

json { "lint-staged": { "*.+(js|jsx)": "eslint --fix", "*.+(json|css|md)": "prettier --write" } }

This configuration will allow you to run ESLint and fix both .js and .jsx files. In the same time, it will run Prettier on .json, .css and .md files.

Setup ESLint and Prettier on a React app with a precommit

In this part, we will see how to setup Prettier, husky, and lint-staged on an app generated by create-react-app. More precisely, we will:

  1. Add the Prettier's recommended configuration to ESLint and add the following formatting rules (see Prettier's options):

  2. The printWidth must be set to 90

  3. There should be no spaces between brackets in object literals.
  4. There should be trailing commas where valid in ES5

  5. Add husky and lint-staged to lint and format only staged files:

  6. There should be a pre-commit hook that runs lint-staged

  7. Non-javascript files (CSS, JSON, HTML, etc.) should be formatted using Prettier
  8. JS and JSX files should be linted (and reformatted thanks to Prettier's plugin for ESLint)

Let's create a React app. Go to the folder you usually work in and run:

shell npx create-react-app react-eslint-prettier-app

Note: If you're not able to use npx, it probably means you're using an outdated version of npm (< 5.2). To solve it, either update npm or install create-react-app globally using npm install -g.

First, let's see how to setup Prettier. For that, we need to install in our devDependencies Prettier and its plugins:

shell npm install --save-dev prettier eslint-config-prettier eslint-plugin-prettier

As a reminder, eslint-config-prettier will disable all ESLint formatting rules that may conflict with Prettier's rules. eslint-plugin-prettier is the plugin that will add Prettier's formatting rules.

Then, let's tell ESLint we'll use Prettier's recommended configuration:

json { "eslintConfig": { "extends": [ "react-app", "react-app/jest", "plugin:prettier/recommended" ] }

Once done, we need to specify the options to Prettier. For that, either create a .prettierrc file or add a prettier key to the package.json. We'll choose the second option:

json { "prettier": { "printWidth": 90, "bracketSpacing": false, "trailingComma": "es5" } }

Let's add the pre-commit hook on staged files. We need to install both husky and lint-staged in the devDependencies for that:

shell npm install --save-dev husky lint-staged

Then, we'll add the pre-commit hook itself by adding a husky key to package.json:

json { "husky": { "hooks": { "pre-commit": "lint-staged" } } }

If we leave this as-is, it will fail because we haven't specified the configuration for lint-staged, so let's do it right away:

json { "lint-staged": { "*.+(js|jsx)": ["eslint --fix", "git add"], "*.+(json|css|md)": ["prettier --write", "git add"] } }

Here we use ESLint to lint and format JS and JSX files. We also use Prettier to format json, css and markdown files. And voilà! You're perfectly set up. Here is the full package.json file:

json { "name": "react-eslint-prettier-app", "version": "0.1.0", "private": true, "dependencies": { ... }, "scripts": { ... }, "eslintConfig": { "extends": [ "react-app", "react-app/jest", "plugin:prettier/recommended" ] }, "prettier": { "printWidth": 90, "bracketSpacing": false, "trailingComma": "es5" }, "husky": { "hooks": { "pre-commit": "lint-staged" } }, "lint-staged": { "*.+(js|jsx)": "eslint --fix", "*.+(json|css|md)": "prettier --write" }, "browserslist": { ... }, "devDependencies": { "eslint-config-prettier": "^7.2.0", "eslint-plugin-prettier": "^3.3.1", "husky": "^4.3.8", "lint-staged": "^10.5.3", "prettier": "^2.2.1" } }

If all goes well, you should have a pre-commit hook that both lints and format your code.