Setting Up a Strict ESLint Config for your React + Typescript Project
In addition to green tests, there's nothing more satisfying than writing consistent and maintainable code with your team. For web projects, you probably have ESLint set up. If you're unsure or want an even stricter rule, read along.
TL;DR
{
"root": true,
"extends": [
"airbnb",
"airbnb-typescript",
"airbnb/hooks",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"plugin:prettier/recommended" // Add this if you're using prettier so it plays well with ESLint
],
"ignorePatterns": [], // Add your ignores here like `"coverage/*"` or `"build/*"`
"parserOptions": {
"project": "./tsconfig.json"
},
"rules": {
// Ordering of imports
"import/order": [
"warn",
{
"groups": [
"builtin",
"external",
"internal",
"parent",
"sibling",
"index",
"object"
],
"alphabetize": {
"order": "asc"
},
"warnOnUnassignedImports": true
}
],
// Add this if you're using React 17+ with support for the new JSX Transform
"react/react-in-jsx-scope": "off",
// Extend airbnb config to allow default arguments in addition to defaultProps
"react/require-default-props": [
"error",
{
"forbidDefaultForRequired": true,
"functions": "defaultArguments"
}
],
// Prefer type imports
"@typescript-eslint/consistent-type-imports": "error",
// Defaulted to warn but we want it strict
"@typescript-eslint/no-unused-vars": "error",
// Allow void for statements. Helpful for @typescript-eslint/no-floating-promises
"no-void": [
"error",
{
"allowAsStatement": true
}
],
// Restrict the use of React.FC and React.VFC (including the longer aliases)
"@typescript-eslint/ban-types": [
"error",
{
"types": {
"React.FC": "React.FC is unnecessary: it provides next to no benefits and has a few downsides. (see https://github.com/facebook/create-react-app/pull/8177)",
"React.FunctionComponent": "React.FunctionComponent is unnecessary: it provides next to no benefits and has a few downsides. (see https://github.com/facebook/create-react-app/pull/8177)",
"React.VFC": "React.VFC and React.VoidFunctionComponent were deprecated in React 18",
"React.VoidFunctionComponent": "React.VFC and React.VoidFunctionComponent were deprecated in React 18"
}
}
]
// Uncomment the rules below if you find @typescript-eslint/no-unsafe-* overly strict:
//
// It's quite difficult to comply with @typescript-eslint/no-unsafe-* rules especially when
// importing packages that are not typed correctly so we'll show warnings instead
// "@typescript-eslint/no-unsafe-argument": "warn",
// "@typescript-eslint/no-unsafe-assignment": "warn",
// "@typescript-eslint/no-unsafe-call": "warn",
// "@typescript-eslint/no-unsafe-member-access": "warn",
// "@typescript-eslint/no-unsafe-return": "warn"
}
}
Click here to use a non-Typescript config
{
"root": true,
"extends": [
"airbnb",
"airbnb/hooks",
"plugin:prettier/recommended" // Add this if you're using prettier so it plays well with ESLint
],
"ignorePatterns": [], // Add your ignores here like `"coverage/*"` or `"build/*"`
"rules": {
// Ordering of imports
"import/order": [
"warn",
{
"groups": [
"builtin",
"external",
"internal",
"parent",
"sibling",
"index",
"object"
],
"alphabetize": {
"order": "asc"
},
"warnOnUnassignedImports": true
}
],
// Add this if you're using React 17+ with support for the new JSX Transform
"react/react-in-jsx-scope": "off",
// Extend airbnb config to allow default arguments in addition to defaultProps
"react/require-default-props": [
"error",
{
"forbidDefaultForRequired": true,
"functions": "defaultArguments"
}
]
}
}
Setting up
In addition to ESlint and TypeScript, we will make use of the following packages:
-
eslint-config-airbnb
-
eslint-config-airbnb-typescript
-
@typescript-eslint/eslint-plugin
-
@typescript-eslint/parser
-
eslint-plugin-import
-
eslint-plugin-react
-
eslint-plugin-react-hooks
-
eslint-plugin-jsx-a11y
If you use Prettier (which I highly recommend), we'll also need to install the following:
npm install -D @typescript-eslint/eslint-plugin \
@typescript-eslint/parser \
eslint-config-airbnb \
eslint-config-airbnb-typescript \
eslint-config-prettier \
eslint-plugin-import \
eslint-plugin-jsx-a11y \
eslint-plugin-prettier \
eslint-plugin-react \
eslint-plugin-react-hooks
Configuration
We first "extend" from the eslint config packages that we installed. The rules are extended from left to right, meaning the ordering is important:
{
"extends": [
"airbnb",
"airbnb-typescript",
"airbnb/hooks",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"plugin:prettier/recommended" // Add this if you're using prettier so it plays well with ESLint
],
}
This minimal option for parserOptions
is required by Typescript ESLint
for typed linting:
{
"parserOptions": {
"project": "./tsconfig.json"
}
}
Summary of custom rules
There are various rules that I specified on the config like the import/order
rule
which will make your life much more easier if you ever feel your imports are disorganized.
-import { dateSort, getPosts } from '../lib/posts'
-import Card from '../components/Card'
import type { InferGetStaticPropsType, NextPage } from 'next'
-import Link from 'next/link'
import dynamic from 'next/dynamic'
+import Link from 'next/link'
+import Card from '../components/Card'
+import { dateSort, getPosts } from '../lib/posts'
If your project uses React 17+, then it's best to disable react/react-in-jsx-scope
as it's
enabled by default on the configs we extended from.
-import React from 'react';
-
export default function Hello({ foo = 'foo' }) {
return <div>{foo}</div>;
}
react/require-default-props
uses the default airbnb config with
an additional functions
option set to defaultArguments
. This makes defaultProps
on functional components optional as long as they have default arguments set:
function Hello({ foo = 'foo' }) {
return <div>{foo}</div>;
}
Hello.propTypes = {
foo: PropTypes.string
};
If you want to separate type imports from regular imports, consider enabling @typescript-eslint/consistent-type-imports
:
import { useState } from 'react';
import type { PropsWithChildren } from 'react';
By default, the @typescript-eslint/no-unused-vars
is set to warn
(yellow squiggly line) and usually passes builds depending on your config.
Setting it to error
helps for a stricter codebase.
The no-void
rule is best paired with @typescript-eslint/no-floating-promises
so we can prepend void
to promises that we don't need to then
or catch
:
// assume message.error returns a promise
function onSuccess(e) {
void message.error(`Oh snap! {e.message}`)
}
The types React.FC
and React.VFC
have
been widely discouraged by the community.
To disallow use, add the @typescript-eslint/ban-types
rule. Further discussion on React.FC
can be found
here.
Done
Of course, you are free to adjust the config based on your requirements. Make it your own! 🤩