ESLint: "is defined but never used" en componentes JSX

Ejecutas ESLint en un proyecto React con Vite y configuración flat (ESLint 9), y varios componentes aparecen como no usados aunque se renderizan sin problema:

npx eslint src

src/App.jsx
  1:8   error  'MiComponente' is defined but never used  no-unused-vars
  2:10  error  'Otro' is defined but never used          no-unused-vars

En ese archivo, MiComponente se usa como <MiComponente /> y Otro como <Otro.Hijo />. La aplicación funciona; el error es un falso positivo de la regla no-unused-vars. Este artículo explica por qué ocurre y cómo corregirlo activando una sola regla de eslint-plugin-react, sin llenar el proyecto de errores nuevos.

Por qué pasa

La regla no-unused-vars del núcleo de ESLint no considera que una etiqueta JSX sea un uso de la variable. La documentación de react/jsx-uses-vars lo explica así: el no-unused-vars del núcleo no detecta las variables usadas en JSX, y esa regla del plugin existe para marcarlas como usadas. En la prueba de este artículo se comprobó el mismo efecto: con solo js.configs.recommended y el analizador por defecto, un componente importado y usado únicamente como etiqueta se reporta como no usado.

Esto solo afecta a los nombres que aparecen exclusivamente en JSX. Si el mismo identificador también se usa en código JavaScript normal (por ejemplo, se pasa a una función), la regla lo cuenta como usado y no hay error.

Por qué no lo viste antes: la plantilla de React de create-vite (versión 7.0.0, la que se revisó) incluye en su eslint.config.js esta excepción, que oculta el problema para cualquier nombre que empiece con mayúscula o guion bajo:

'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],

Si tu configuración no tiene esa línea (por ejemplo, porque la quitaste, o porque armaste el archivo solo con js.configs.recommended y los plugins de hooks y refresh), el falso positivo aparece.

Solución

  1. Instala el plugin de React para ESLint.

    npm install --save-dev eslint-plugin-react
  2. Regístralo y activa solo react/jsx-uses-vars. Un eslint.config.js mínimo queda así; si ya tienes un bloque para tus archivos .jsx (con react-hooks o react-refresh), agrega ahí react en plugins y la regla en rules:

    import js from '@eslint/js'
    import globals from 'globals'
    import react from 'eslint-plugin-react'
    
    export default [
      { ignores: ['dist'] },
      js.configs.recommended,
      {
        files: ['**/*.{js,jsx}'],
        plugins: { react },
        languageOptions: {
          ecmaVersion: 'latest',
          globals: globals.browser,
          parserOptions: {
            ecmaFeatures: { jsx: true },
            sourceType: 'module',
          },
        },
        rules: {
          'react/jsx-uses-vars': 'error',
        },
      },
    ]

    El nombre del plugin en plugins (react) define el prefijo de las reglas (react/...). Según la documentación de la regla, react/jsx-uses-vars solo tiene efecto si no-unused-vars está activada, algo que ya hace js.configs.recommended.

  3. Opcional: detecta imports que faltan. Como ESLint no ve las etiquetas JSX como referencias, tampoco avisa cuando usas un componente que nunca importaste (no-undef guarda silencio y el error aparece recién al ejecutar la app). Esta regla del mismo plugin lo cubre:

    rules: {
      'react/jsx-uses-vars': 'error',
      'react/jsx-no-undef': 'error',
    },
  4. Evita activar el preset completo solo para esto. react.configs.flat.recommended incluye muchas más reglas. En la prueba, un componente tan simple como function Tarjeta({ titulo, contenido }) sin PropTypes generó errores react/prop-types por cada propiedad, además de un aviso de que no se había definido la versión de React. En un proyecto real con muchos componentes, eso puede sumar cientos de errores que no tienen relación con el falso positivo. Si más adelante quieres el preset, agrega también react.configs.flat['jsx-runtime'] (la documentación del plugin lo indica para React 17 o superior), define settings.react.version y decide si desactivas 'react/prop-types': 'off'.

Cómo verificar que quedó resuelto

npx eslint src

Los errores de componentes usados solo en JSX deben desaparecer. Lo que quede son problemas reales: en la prueba, el único error restante fue un componente sin importar, reportado por react/jsx-no-undef.

Para confirmar que la regla está activa en un archivo concreto, en PowerShell:

npx eslint --print-config src/App.jsx | Select-String 'jsx-uses-vars'

Para comprobar que no apagaste la detección general, deja un import sin usar en cualquier archivo, por ejemplo import { sinUsar } from './util.js'. ESLint debe seguir reportando 'sinUsar' is defined but never used.

Variantes y errores relacionados

La alternativa de la plantilla: varsIgnorePattern

Puedes volver al enfoque de la plantilla de Vite con 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }]. La opción varsIgnorePattern exime de la comprobación a las variables cuyo nombre coincide con la expresión regular. Funciona, pero a costa de que ESLint deje de avisar de cualquier variable, función o importación con nombre en mayúscula que realmente no se use. Con react/jsx-uses-vars mantienes la detección completa.

Proyectos con TypeScript

Con typescript-eslint el problema no apareció en la prueba: un componente importado y usado solo como etiqueta en un archivo .tsx no se reportó, mientras que un import realmente sin usar sí. Si ves este error en un proyecto TypeScript, revisa que el analizador de typescript-eslint esté aplicado a esos archivos.

¿Y react/jsx-uses-react?

Sirve para el caso en que importas React solo para que la transformación JSX clásica lo use. Con la transformación automática de JSX (React 17 o superior), no necesitas importar React en cada archivo: los archivos de la plantilla de React de create-vite que se revisó (main.jsx y App.jsx) no lo importan. El README del plugin indica que la configuración jsx-runtime desactiva esta regla.

La regla no tiene efecto

Revisa tres cosas: que el plugin esté registrado en plugins con el mismo nombre que usas en el prefijo de la regla, que el bloque tenga un files que incluya tus archivos .jsx, y que no-unused-vars esté realmente activada para esos archivos.

Resumen

  • El no-unused-vars del núcleo de ESLint no cuenta las etiquetas JSX como uso, así que marca componentes que sí renderizas.
  • La plantilla de Vite lo oculta con varsIgnorePattern: '^[A-Z_]'; sin esa línea, el falso positivo aparece.
  • Instala eslint-plugin-react y activa solo 'react/jsx-uses-vars': 'error'.
  • No actives el preset completo solo para esto: añade errores como react/prop-types que no tienen relación.
  • Añade react/jsx-no-undef si quieres que ESLint detecte componentes sin importar.

Referencias

Comentarios