Skip to content

Fix: ESLint no-unused-vars False Positives and Configuration

FixDevs ·

Quick Answer

How to fix ESLint no-unused-vars false positives — TypeScript types, destructuring ignores, React imports, function arguments, and configuring the rule to match your codebase patterns.

The Error

ESLint reports unused variable errors for variables that are actually used, or for intentionally unused ones:

error  'React' is defined but never used  no-unused-vars
error  'MyType' is defined but never used  no-unused-vars
error  '_event' is defined but never used  no-unused-vars
error  'props' is defined but never used   no-unused-vars

Or TypeScript-specific false positives:

error  'FC' is defined but never used  no-unused-vars

The variable React is required for JSX transformation in older setups but isn’t referenced explicitly. MyType is a TypeScript type used only in type annotations. _event is an intentionally ignored parameter.

Why This Happens

The base ESLint no-unused-vars rule doesn’t understand:

  • TypeScript types and interfacestype MyType and interface MyInterface are only used in type annotations, which no-unused-vars ignores (it’s a JavaScript rule, not TypeScript-aware).
  • React import for JSX — before React 17’s new JSX transform, import React from 'react' was required for JSX but not explicitly referenced in code.
  • Intentionally unused parameters — callback parameters you must declare by position but don’t use ((_event, value) => value).
  • Destructuring for exclusionconst { secret, ...rest } = objsecret is used to exclude it from rest, but ESLint sees it as unused.
  • Globals from other files — variables declared in one file but used via global scope in another.

Fix 1: Use @typescript-eslint/no-unused-vars for TypeScript Projects

The base no-unused-vars rule doesn’t understand TypeScript. Replace it with the TypeScript-aware version:

// .eslintrc.js
module.exports = {
  extends: [
    'plugin:@typescript-eslint/recommended',
  ],
  rules: {
    // Disable the base rule — it reports false positives for TypeScript types
    'no-unused-vars': 'off',
    // Enable the TypeScript-aware version
    '@typescript-eslint/no-unused-vars': ['error', {
      argsIgnorePattern: '^_',       // Ignore args starting with _
      varsIgnorePattern: '^_',       // Ignore vars starting with _
      caughtErrorsIgnorePattern: '^_',  // Ignore caught error vars starting with _
    }],
  },
};
// .eslintrc.json equivalent
{
  "rules": {
    "no-unused-vars": "off",
    "@typescript-eslint/no-unused-vars": ["error", {
      "argsIgnorePattern": "^_",
      "varsIgnorePattern": "^_",
      "caughtErrorsIgnorePattern": "^_"
    }]
  }
}

@typescript-eslint/no-unused-vars correctly handles:

  • Type imports (import type { MyType })
  • Interfaces and type aliases used only in annotations
  • Enums used as types

Fix 2: Fix React Import False Positive

In React 17+ with the new JSX transform, you don’t need to import React for JSX. Configure your bundler/transpiler and ESLint to match:

If using React 17+ new JSX transform — remove the import and update tsconfig/babel:

// tsconfig.json
{
  "compilerOptions": {
    "jsx": "react-jsx"  // New transform — no React import needed
  }
}
// .eslintrc.js — tell ESLint you're using the new transform
module.exports = {
  extends: [
    'plugin:react/recommended',
    'plugin:react/jsx-runtime',  // ← Disables the react/react-in-jsx-scope rule
  ],
};

If using React 16 or can’t migrate — tell ESLint that React is a global:

// .eslintrc.js
module.exports = {
  globals: {
    React: 'readonly',
  },
};

Or add React to your eslint config’s environment:

// .eslintrc.js
module.exports = {
  rules: {
    'no-unused-vars': ['error', {
      varsIgnorePattern: '^React$',
    }],
  },
};

Fix 3: Use Underscore Prefix for Intentionally Unused Variables

The convention for intentionally unused variables is to prefix them with _. Configure the rule to ignore this pattern:

// .eslintrc.js
module.exports = {
  rules: {
    '@typescript-eslint/no-unused-vars': ['error', {
      argsIgnorePattern: '^_',
      varsIgnorePattern: '^_',
      caughtErrorsIgnorePattern: '^_',
      destructuredArrayIgnorePattern: '^_',  // For array destructuring
    }],
  },
};
// Now these don't trigger the rule:

// Callback where you need the second arg but not the first
array.forEach((_item, index) => console.log(index));

// Required parameter position but unused
button.addEventListener('click', (_event) => handleClick());

// Caught error when you only want to swallow it
try {
  riskyOp();
} catch (_err) {
  // Intentionally ignored
}

// Destructuring to exclude a field from the rest
const { password: _password, ...safeUser } = user;
sendToClient(safeUser);

Pro Tip: Using _ as a prefix is more communicative than just ignoring the lint rule — it signals to readers that the variable is intentionally not used, rather than accidentally forgotten.

Fix 4: Fix Type Import False Positives

When TypeScript types are imported and only used in type positions, ESLint’s base rule flags them as unused:

// ESLint flags 'User' and 'Config' as unused
import { User, Config } from './types';

function setup(config: Config): User {  // Used in type positions only
  return { id: 1, name: 'Alice' };
}

Fix with import type syntax:

// Use 'import type' for type-only imports
import type { User, Config } from './types';
// ESLint (with @typescript-eslint rules) correctly recognizes these as type-only

Or configure the rule to handle type imports:

// .eslintrc.js
module.exports = {
  rules: {
    '@typescript-eslint/no-unused-vars': ['error', {
      // 'all' checks all variables including type parameters
      vars: 'all',
      // 'after-used' ignores unused args before the last used one
      args: 'after-used',
    }],
    // Also enable this to enforce 'import type' for type-only imports
    '@typescript-eslint/consistent-type-imports': 'error',
  },
};

Fix 5: Fix Destructuring Unused Variables

When destructuring to extract some properties and pass the rest:

// ESLint flags 'id' as unused
const { id, ...rest } = user;
return rest;  // id is used to EXCLUDE it, but ESLint doesn't know

// Fix option 1: underscore prefix
const { id: _id, ...rest } = user;

// Fix option 2: disable for that line
const { id, ...rest } = user; // eslint-disable-line @typescript-eslint/no-unused-vars

// Fix option 3: use Object.fromEntries
const { id: _, ...rest } = user;  // '_' is commonly used as throwaway name

For array destructuring:

// Skipping elements with empty slots
const [, second, , fourth] = array;

// Or with underscore convention
const [_first, second, _third, fourth] = array;

Fix 6: Disable the Rule for Specific Lines or Blocks

When the rule gives a false positive that you can’t fix with configuration, disable it inline:

// Disable for one line
const unusedButRequired = setup(); // eslint-disable-line @typescript-eslint/no-unused-vars

// Disable for the next line
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const result = initializeLibrary();

// Disable for a whole block (use sparingly)
/* eslint-disable @typescript-eslint/no-unused-vars */
const a = 1;
const b = 2;
/* eslint-enable @typescript-eslint/no-unused-vars */

// Disable for the whole file (avoid this)
/* eslint-disable no-unused-vars */

Inline disables should be used as a last resort with a comment explaining why:

// eslint-disable-next-line @typescript-eslint/no-unused-vars -- required by EventEmitter interface
const error = emitter.on('error', () => {});

Fix 7: Configure Global Variables

If variables are defined globally (via a script tag, browser globals, or a setup file) and used across files, declare them as globals to prevent false positives:

// .eslintrc.js
module.exports = {
  globals: {
    // Browser globals not in the 'browser' environment
    gtag: 'readonly',
    Stripe: 'readonly',
    __DEV__: 'readonly',
    __VERSION__: 'readonly',
  },
  env: {
    browser: true,    // window, document, fetch, etc.
    node: true,       // process, require, __dirname, etc.
    es2022: true,     // Modern JS globals
  },
};

For Jest globals:

module.exports = {
  env: {
    'jest/globals': true,  // describe, it, expect, beforeEach, etc.
  },
  plugins: ['jest'],
};

Still Not Working?

Check which rule is actually firing. The error message shows the rule name in brackets:

error  'foo' is defined but never used  no-unused-vars
                                        ^^^^^^^^^^^^^^^

If it’s no-unused-vars (base rule), switch to @typescript-eslint/no-unused-vars. If it’s already the TypeScript version but still firing, check the rule configuration.

Check ESLint config file precedence. ESLint merges configs from parent directories. A no-unused-vars: error in a root .eslintrc might override your project-level off setting:

# See what config ESLint is using for a specific file
npx eslint --print-config src/index.ts

Verify the plugin is installed:

npm list @typescript-eslint/eslint-plugin
# If missing:
npm install -D @typescript-eslint/eslint-plugin @typescript-eslint/parser

Check for extends ordering. Later entries in extends override earlier ones:

// @typescript-eslint/recommended enables no-unused-vars
// If 'recommended' comes AFTER your custom rules, it overrides them
module.exports = {
  extends: [
    'plugin:@typescript-eslint/recommended',  // Enables the rule
  ],
  rules: {
    // This correctly overrides the extended rule
    '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
  },
};

For related ESLint issues, see Fix: ESLint Parsing Error Unexpected Token and Fix: ESLint import/no-unresolved.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles