Typescript-eslint + prettier for code standardization in React with Typescript

posted Originally published at dev.to 7 min read

Introduction

In this article, I'll show you how to use typescript-eslint and prettier to standardize code in a React project with Typescript.
The main goal is to demonstrate how to set up the libraries, add plugins, customize rules, and apply some additional configurations.

Libs

  • typescript-eslint: responsible for analyzing the code to identify and resolve issues
  • prettier: responsible for code formatting

Setup typescript-eslint

To add typescript-eslint:

yarn add typescript-eslint eslint @eslint/js --dev

  • typescript-eslint: allows eslint to parse typescript syntax and provides linting rules for typescript.
  • eslint: dependency required by typescript-eslint
  • @eslint/js: provides eslint rules

Generate a configuration file at the root of the project:

  • eslint.config.mjs
import eslint from "@eslint/js";
import tseslint from "typescript-eslint";

export default tseslint.config(
  eslint.configs.recommended,
  tseslint.configs.recommendedTypeChecked,
  {
    languageOptions: {
      parserOptions: {
        projectService: true,
        tsconfigRootDir: import.meta.dirname,
      },
    },
  },
  {
    settings: {
      react: {
        version: "detect",
      },
    }
  }
);
  • tseslint.config: where the typescript-eslint configurations are specified
  • eslint.configs.recommended: applies the recommended eslint rules in the code analysis
  • tseslint.configs.recommendedTypeChecked: applies the recommended typescript rules in the code analysis, that additionally require type information
  • projectService: indicates to ask typescript's type checking service for each source file's type information
  • import.meta.dirname: tells the parser the absolute path of project's root directory
  • settings: it is set to detect the version of React being used in the project

Adding plugins

The following libraries will be added to apply React and hooks rules:

yarn add eslint-plugin-react eslint-plugin-react-hooks --dev

  • eslint-plugin-react: brings linting rules for React
  • eslint-plugin-react-hooks: brings hooks rules

Adding the plugins in the eslint configuration file:

  • eslint.config.mjs
// @ts-nocheck

import eslint from "@eslint/js";
import reactPlugin from "eslint-plugin-react";
import reactHooks from "eslint-plugin-react-hooks";
import tseslint from "typescript-eslint";

export default tseslint.config(
  eslint.configs.recommended,
  tseslint.configs.recommendedTypeChecked,
  {
    languageOptions: {
      parserOptions: {
        projectService: true,
        tsconfigRootDir: import.meta.dirname,
      },
    },
  },
  reactPlugin.configs.flat.recommended,
  reactPlugin.configs.flat['jsx-runtime'],
  {
    settings: {
      react: {
        version: "detect",
      },
    },
    plugins: {
      "react-hooks": reactHooks,
    },
    rules: {
      ...reactHooks.configs.recommended.rules,
    }
  }
);
  • // @ts-nocheck: Used to ignore the type check for this file due to an issue with eslint-plugin-react-hooks related to missing type declarations
  • reactPlugin.configs.flat.recommended: applies the recommended React rules in the code analysis
  • reactPlugin.configs.flat['jsx-runtime']: required for React 17+ to work with the new jsx runtime introduced in that version
  • plugins: add the eslint-plugin-react-hooks plugin to extend eslint
  • rules: add customizable rules, in the case of eslint-plugin-react-hooks, the recommended rules are included

Adding prettier

The lib will be added:

yarn add prettier --dev

A configuration file named .prettierrc is created at the root, initially empty since the default rules of the library are not being modified.

Customization of rules

At the moment, the configuration of typescript-eslint and prettier has been done, applying the recommended rules from the added plugins and the default from prettier. However, inside a project, some of these rules might be interesting to modify, and this can be done through the eslint and prettier configuration file.

Prettier customization

To customize prettier rules, use the following structure:

{
  rule: rule_expectation,
  rule: rule_expectation
}
  • rule: corresponds to the rule that will be customized
  • rule_expectation: corresponds to what is expected for the code to follow

At the moment, the eslint configuration file is using the default prettier rules, among which are some rules to not use single quotes for code and jsx. Starting from an example where the project wants to define the use of single quotes, it would look like this in the configuration file:

  • .prettierrc
{
  "singleQuote": true,
  "jsxSingleQuote": true
}

Eslint customization

To customize eslint rules, use the following structure:

"rules": {
  rule: error_type,
  rule: [error_type, rule_expectation]
}
  • rule: corresponds to the rule that will be customized
  • error_type: it can be warn, which returns a warning if the rule is not satisfied; error, which returns an error if the rule is not satisfied; or off, which disables the rule
  • rule_expectation: corresponds to what is expected for the code to follow

In the case of a rule that already has an expectation defined inside it, rule: error_type is passed directly. For rules that can have multiple expectations, rule: [error_type, rule_expectation] is passed, defining what is expected from it.

At the moment, the eslint configuration file is using the recommended rules from eslint, typescript-eslint, react and react-hooks. Starting from an example where the project wants to define a customizable rule for each of them, it would look like this in the configuration file:

// @ts-nocheck

import eslint from "@eslint/js";
import reactPlugin from "eslint-plugin-react";
import reactHooks from "eslint-plugin-react-hooks";
import tseslint from "typescript-eslint";

export default tseslint.config(
  eslint.configs.recommended,
  tseslint.configs.recommendedTypeChecked,
  {
    languageOptions: {
      parserOptions: {
        projectService: true,
        tsconfigRootDir: import.meta.dirname,
      },
    },
  },
  reactPlugin.configs.flat.recommended,
  reactPlugin.configs.flat['jsx-runtime'],
  {
    settings: {
      react: {
        version: "detect",
      },
    },
    plugins: {
      "react-hooks": reactHooks,
    },
    rules: {
      ...reactHooks.configs.recommended.rules,
      "no-console": "warn", // eslint rule
      "react/jsx-no-useless-fragment": "error", // React rule
      "react-hooks/exhaustive-deps": "off", // hooks rule
      "@typescript-eslint/no-unused-vars": "off", // typescript rule
    }
  }
);

Ignore files

There will be certain types of files for which you don't want the typescript-eslint to run. To do this, you can add ignores in the configuration file. As an example, for an app that ignores documentation files with storybook in the standardization, it would look like this:

// @ts-nocheck

import eslint from "@eslint/js";
import reactPlugin from "eslint-plugin-react";
import reactHooks from "eslint-plugin-react-hooks";
import tseslint from "typescript-eslint";

export default tseslint.config(
  eslint.configs.recommended,
  tseslint.configs.recommendedTypeChecked,
  {
    languageOptions: {
      parserOptions: {
        projectService: true,
        tsconfigRootDir: import.meta.dirname,
      },
    },
  },
  reactPlugin.configs.flat.recommended,
  reactPlugin.configs.flat['jsx-runtime'],
  // the rules will not apply to these types of files
  {
    ignores: ["**/*.stories.tsx"],
  },
  {
    settings: {
      react: {
        version: "detect",
      },
    },
    plugins: {
      "react-hooks": reactHooks,
    },
    rules: {
      ...reactHooks.configs.recommended.rules,
      "no-console": "warn",
      "react/jsx-no-useless-fragment": "error",
      "react-hooks/exhaustive-deps": "off",
      "@typescript-eslint/no-unused-vars": "off",
    }
  }
);

Specific rules for certain types of files

In addition to defining files to exclude from running typescript-eslint, it's also possible to define customizable rules specifically for certain files by setting them alongside files. As an example, for an app that disables a rule for test files, it would look like this:

// @ts-nocheck

import eslint from "@eslint/js";
import reactPlugin from "eslint-plugin-react";
import reactHooks from "eslint-plugin-react-hooks";
import tseslint from "typescript-eslint";

export default tseslint.config(
  eslint.configs.recommended,
  tseslint.configs.recommendedTypeChecked,
  {
    languageOptions: {
      parserOptions: {
        projectService: true,
        tsconfigRootDir: import.meta.dirname,
      },
    },
  },
  reactPlugin.configs.flat.recommended,
  reactPlugin.configs.flat['jsx-runtime'],
  {
    ignores: ["**/*.stories.tsx"],
  },
  {
    settings: {
      react: {
        version: "detect",
      },
    },
    plugins: {
      "react-hooks": reactHooks,
    },
    rules: {
      ...reactHooks.configs.recommended.rules,
      "no-console": "warn",
      "react/jsx-no-useless-fragment": "error",
      "react-hooks/exhaustive-deps": "off",
      "@typescript-eslint/no-unused-vars": "off",
    }
  },
  // specific files with specific rules for them
  {
    files: ["**/*.test.tsx"],
    rules: {
      "@typescript-eslint/no-unused-expressions": "off",
    },
  }
);

Script package.json

To run eslint and check the application's code, run prettier and check the application's formatting, scripts will be added to the package.json:

// ...
"scripts": {
  // ...
  "lint": "eslint .",
  "lint-fix": "eslint . --fix",
  "lint-src": "eslint src",
  "lint-src-fix": "eslint src --fix",
  "format": "prettier . --check",
  "format-fix": "prettier . --write"
  "format-src": "prettier src --check",
  "format-src-fix": "prettier src --write"
}
  • lint: it will check all the application's files, following the rules defined in the eslint configuration file
  • lint-fix: it will auto-correct the code for the entire application, following the rules defined in the eslint configuration file
  • lint-src: it will check all the files inside the src folder (I used src as an example, but it could be any directory inside the app where these checks are necessary), following the rules defined in the eslint configuration file
  • lint-src-fix: it will auto-correct all the files inside the src folder, following the rules defined in the eslint configuration file
  • format: it will check all the application's files formatting, following the rules defined in the prettier configuration file
  • format-fix: it will auto-correct the formatting for the entire application, following the rules defined in the prettier configuration file
  • format-src: it will check all the files inside the src folder formatting (I used src as an example, but it could be any directory inside the app where these checks are necessary), following the rules defined in the prettier configuration file
  • format-src-fix: it will auto-correct the formatting for all the files inside the src folder, following the rules defined in the prettier configuration file

Article updates

Initially, the article had configured typescript-eslint to run together with prettier, it was using tseslint.configs.recommended instead of tseslint.configs.recommendedTypeChecked and mentioned that it wasn't possible to use the eslint-plugin-react-hooks plugin. However, Josh Goldberg, a team member of the linters, pointed out three interesting points that made me update the article:

For this reason, I modified it to use tseslint.configs.recommendedTypeChecked, separated typescript-eslint from prettier (each with its own responsibility) and added eslint-plugin-react-hooks.

Conclusion

The idea of this article is to present how to configure typescript-eslint and prettier, adding plugins, and showing how to customize rules. This article was originally published on dev.to. I’ve only made a small update to the introduction.

If you read this far, tweet to the author to show them you care. Tweet a Thanks

Great breakdown—thanks for sharing your setup for combining typescript-eslint and Prettier in React projects! How do you handle rule conflicts between eslint and Prettier, and have you found any gotchas with disabling rules for hooks or TypeScript? Would love to hear your experience.

Thanks for your comment!

Given the setup above, which uses the recommended rules from typescript-eslint, eslint and react plugin, these configurations currently do not enable any formatting rules that conflict with prettier, as can be seen in the links shared in the Eslint customization section. This is also explained by one of the team members of the linters, Josh Goldberg, in the following article: You Probably Don't Need eslint-config-prettier or eslint-plugin-prettier.

What you need to be more careful with is if you use a different type of configuration - in that case, you should check if there are any formatting rules enabled similar to prettier rules and disable them inside the rules section of your eslint.config.mjs.

One thing I also make sure to do is the order of execution: I run eslint first, then run prettier to finalize formatting.

Regarding rules, one I see many projects disabling is react-hooks/exhaustive-deps. Strictly following it by including everything as a dependency of useEffect sometimes causes excessives re-renders, which can lead to bugs.

For typescript, I disabled the @typescript-eslint/no-unused-vars rule because it was triggering warnings in a component library where the React version is recent, but I include import React from "react" in all components to allow compatibility with users on older React versions that adds the library, so I didn't want that to cause issues.
Additionally I disabled two other rules just for test files: @typescript-eslint/no-unused-expressions and @typescript-eslint/unbound-method, because they prevent using certain Jest and testing library expectations, such as: expect(iconButtonElement).not.toBeDisabled

More Posts

Documentation of components in React with TypeScript using Storybook (version 8)

Eduardo Gris - Aug 14

React + TypeScript — The Practical Refresher

Sibasish Mohanty - Sep 9

Starting a new web app with a React project, should you go with React JavaScript or TypeScript?

Sunny - Jun 24

Build a Full-Stack Video Streaming App with React.js, Node.js, Next.js, MongoDB, Bunny CDN, and Material UI

torver213 - Mar 29

Build a Multilingual Blog Viewer in React using Intlayer

paruidev - Aug 17
chevron_left