I'm trying to set up the same ESLint configuration in my new React 19 + Vite app using the modern eslint.config.js format. However, it's not working — the linter doesn’t show any errors or warnings in the editor.
New eslint.config.js
import globals from "globals";
import pluginReact from "eslint-plugin-react";
import { defineConfig } from "eslint/config";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import prettier from "eslint-plugin-prettier";
import importPlugin from "eslint-plugin-import";
import react from "eslint-plugin-react";
import jsxA11y from "eslint-plugin-jsx-a11y";
export default defineConfig([
{ ignores: ["dist"] },
{
files: ["**/*.{js,mjs,cjs,jsx}"],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: "latest",
ecmaFeatures: { jsx: true },
sourceType: "module",
},
},
plugins: {
"react-hooks": reactHooks,
"react-refresh": reactRefresh,
prettier,
import: importPlugin,
react,
"jsx-a11y": jsxA11y,
},
rules: {
...js.configs.recommended.rules,
...reactHooks.configs.recommended.rules,
// "no-unused-vars": ["error", { varsIgnorePattern: "^[A-Z_]" }],
// "no-undef": ["error", { globals: { process: true } }],
// Vite React specific
"react-refresh/only-export-components": [
"warn",
{ allowConstantExport: true },
],
// Your custom rules from old config
"prettier/prettier": "off",
"no-nested-ternary": "off",
"no-underscore-dangle": "off",
"default-param-last": "off",
"prefer-arrow-callback": "error",
camelcase: ["error", { properties: "never", ignoreDestructuring: true }],
"no-else-return": ["error", { allowElseIf: true }],
"no-param-reassign": ["error", { props: false }],
// Import rules
"import/no-extraneous-dependencies": [
"error",
{
devDependencies: false,
optionalDependencies: false,
peerDependencies: false,
},
],
"import/order": [
"error",
{
pathGroups: [
{
pattern: "react",
group: "external",
position: "before",
},
],
pathGroupsExcludedImportTypes: ["builtin"],
},
],
"import/no-unused-modules": ["error", { unusedExports: true }],
// React rules
"react/require-default-props": "off",
"react/jsx-no-undef": ["error", { allowGlobals: true }],
"react/jsx-uses-vars": "error",
// Best Practices
eqeqeq: "error",
"no-invalid-this": "error",
"no-return-assign": "error",
"no-unused-expressions": ["error", { allowTernary: true }],
"no-useless-concat": "error",
"no-useless-return": "error",
"no-use-before-define": "error",
"no-duplicate-imports": "error",
"no-plusplus": ["error", { allowForLoopAfterthoughts: true }],
// Variable and import rules
"no-undef": "error",
"no-unused-vars": [
"error",
{
vars: "all",
args: "after-used",
ignoreRestSiblings: true,
varsIgnorePattern: "^[A-Z_]", // Keep the pattern from new config
},
],
},
settings: {
react: {
version: "detect",
},
},
},
pluginReact.configs.recommended,
]);
This is the ESLint config I used successfully in my older CRA app (.eslintrc.js):
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
extends: ["prettier"],
plugins: ["prettier", "import", "react", "react-hooks", "jsx-a11y"],
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 12,
sourceType: "module",
},
rules: {
"prettier/prettier": "off",
"no-nested-ternary": "off",
"react/require-default-props": "off",
"no-underscore-dangle": ["off"],
"default-param-last": "off",
"prefer-arrow-callback": "error",
camelcase: ["error", { properties: "never", ignoreDestructuring: true }],
"no-else-return": ["error", { allowElseIf: true }],
"no-param-reassign": ["error", { props: false }],
// Best Practices
"import/no-extraneous-dependencies": [
"error",
{
devDependencies: false,
optionalDependencies: false,
peerDependencies: false,
},
],
eqeqeq: "error",
"no-invalid-this": "error",
"no-return-assign": "error",
"no-unused-expressions": ["error", { allowTernary: true }],
"no-useless-concat": "error",
"no-useless-return": "error",
"no-use-before-define": "error",
"no-duplicate-imports": "error",
"no-plusplus": ["error", { allowForLoopAfterthoughts: true }],
"import/order": [
"error",
{
pathGroups: [
{
pattern: "react",
group: "external",
position: "before",
},
],
pathGroupsExcludedImportTypes: ["builtin"],
},
],
// Ensure components are imported if used
"react/jsx-no-undef": ["error", { allowGlobals: true }],
// Ensure all variables are declared before use
"no-undef": "error",
// Ensure there are no unused variables
"no-unused-vars": ["error", { vars: "all", args: "after-used", ignoreRestSiblings: true }],
// Ensure there are no unused imports
"import/no-unused-modules": ["error", { unusedExports: true }],
// Mark variables used in JSX as used
"react/jsx-uses-vars": "error",
},
settings: {
react: {
version: "detect",
},
},
};
I did try npm run lint but I get this:
Oops! Something went wrong! :(
ESLint: 9.28.0
ESLint couldn't find a configuration file. To set up a configuration file for this project, please run:
npm init @eslint/config@latest
And if I run this command, it resets full eslint.config.js file
But I’m not sure how to properly convert or apply it to the new flat config (eslint.config.js). Any ideas on how to correctly set it up so ESLint works in the new environment?
Your flat config has some issues, so let's go one by one:
import js from '@eslint/js';
Since you're using the following rules: ...js.configs.recommended.rules.
You're importing eslint-plugin-react twice, this isn't an error but it's better to just import it once.
The last line where you're extending the config pluginReact.configs.recommended will not work because it has an older format for plugins. Considering how you already have the plugins and parserOptions manually added onto your config, you should probably just extend the rules:
rules: {
...react.configs.recommended.rules
}
eslint-plugin-import when using the import/no-unused-modules rule. You can fix it by adding an empty .eslintrc file in your project.Finally, your config could look like this:
import js from '@eslint/js';
import globals from "globals";
import { defineConfig } from "eslint/config";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import prettier from "eslint-plugin-prettier";
import importPlugin from "eslint-plugin-import";
import react from "eslint-plugin-react";
import jsxA11y from "eslint-plugin-jsx-a11y";
export default defineConfig([
{ ignores: ["dist"] },
{
files: ["**/*.{js,mjs,cjs,jsx}"],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: "latest",
ecmaFeatures: { jsx: true },
sourceType: "module",
},
},
plugins: {
"react-hooks": reactHooks,
"react-refresh": reactRefresh,
prettier,
import: importPlugin,
react,
"jsx-a11y": jsxA11y,
},
rules: {
...js.configs.recommended.rules,
...reactHooks.configs.recommended.rules,
// "no-unused-vars": ["error", { varsIgnorePattern: "^[A-Z_]" }],
// "no-undef": ["error", { globals: { process: true } }],
// Vite React specific
"react-refresh/only-export-components": [
"warn",
{ allowConstantExport: true },
],
// Your custom rules from old config
"prettier/prettier": "off",
"no-nested-ternary": "off",
"no-underscore-dangle": "off",
"default-param-last": "off",
"prefer-arrow-callback": "error",
camelcase: ["error", { properties: "never", ignoreDestructuring: true }],
"no-else-return": ["error", { allowElseIf: true }],
"no-param-reassign": ["error", { props: false }],
// Import rules
"import/no-extraneous-dependencies": [
"error",
{
devDependencies: false,
optionalDependencies: false,
peerDependencies: false,
},
],
"import/order": [
"error",
{
pathGroups: [
{
pattern: "react",
group: "external",
position: "before",
},
],
pathGroupsExcludedImportTypes: ["builtin"],
},
],
"import/no-unused-modules": ["error", { unusedExports: true }],
// React rules
"react/require-default-props": "off",
"react/jsx-no-undef": ["error", { allowGlobals: true }],
"react/jsx-uses-vars": "error",
...react.configs.recommended.rules,
// Best Practices
eqeqeq: "error",
"no-invalid-this": "error",
"no-return-assign": "error",
"no-unused-expressions": ["error", { allowTernary: true }],
"no-useless-concat": "error",
"no-useless-return": "error",
"no-use-before-define": "error",
"no-duplicate-imports": "error",
"no-plusplus": ["error", { allowForLoopAfterthoughts: true }],
// Variable and import rules
"no-undef": "error",
"no-unused-vars": [
"error",
{
vars: "all",
args: "after-used",
ignoreRestSiblings: true,
varsIgnorePattern: "^[A-Z_]", // Keep the pattern from new config
},
],
},
settings: {
react: {
version: "detect",
},
},
},
]);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With