Fix: ESLint no-unused-vars False Positives and Configuration
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-varsOr TypeScript-specific false positives:
error 'FC' is defined but never used no-unused-varsThe 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 interfaces —
type MyTypeandinterface MyInterfaceare only used in type annotations, whichno-unused-varsignores (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 exclusion —
const { secret, ...rest } = obj—secretis used to exclude it fromrest, 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-onlyOr 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 nameFor 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.tsVerify the plugin is installed:
npm list @typescript-eslint/eslint-plugin
# If missing:
npm install -D @typescript-eslint/eslint-plugin @typescript-eslint/parserCheck 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.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: ESLint Config Not Working — Rules Ignored, Flat Config Errors, or Plugin Not Found
How to fix ESLint configuration issues — flat config vs legacy config, extends conflicts, parser options, plugin resolution, per-directory overrides, and migrating to ESLint 9.
Fix: CodeMirror Not Working — Editor Not Rendering, Extensions Not Loading, or React State Out of Sync
How to fix CodeMirror 6 issues — basic setup, language and theme extensions, React integration, vim mode, collaborative editing, custom keybindings, and read-only mode.
Fix: GSAP Not Working — Animations Not Playing, ScrollTrigger Not Firing, or React Cleanup Issues
How to fix GSAP animation issues — timeline and tween basics, ScrollTrigger setup, React useGSAP hook, cleanup and context, SplitText, stagger animations, and Next.js integration.
Fix: i18next Not Working — Translations Missing, Language Not Switching, or Namespace Errors
How to fix i18next issues — react-i18next setup, translation file loading, namespace configuration, language detection, interpolation, pluralization, and Next.js integration.