ESLint 15 🛡️ React Plugin — eslint-plugin-react and hooks rules
React applications have two distinct categories of lint rules, and they come from two different plugins. eslint-plugin-react provides rules about JSX syntax and component structure: missing key props, unescaped entities, unknown DOM properties, and prop type validation . eslint-plugin-react-hooks provides rules about the Rules of React: the ordering of hooks, the dependency arrays, and the purity constraints that the React Compiler enforces . The two plugins are complementary, and a React project typically installs both.
This chapter covers the two plugins, the flat config setup for each, the recommended configs that bundle the most important rules, and the distinction between the JSX-structure rules and the hooks-behavior rules.
Key point: eslint-plugin-react catches JSX and component-structure issues. eslint-plugin-react-hooks catches violations of the Rules of React. The hooks plugin is the one that the React team maintains, and its recommended config includes the rules that the React Compiler uses to decide whether a component can be safely optimized.
Why the two plugins are separate
The ownership problem. eslint-plugin-react is a community project maintained under the jsx-eslint organization. eslint-plugin-react-hooks is maintained by the React team itself. The two have different release schedules, different maintainers, and different scopes. The community plugin covers the JSX syntax and the component conventions; the official plugin covers the behavioral rules that React itself depends on .
The compiler problem. The React Compiler relies on the eslint-plugin-react-hooks rules to determine which components are safe to optimize. When the compiler detects a violation, it skips that component and keeps the rest of the app compiled. The recommended config includes rules like immutability, purity, and set-state-in-render that the compiler uses to make that decision . The community plugin’s rules are not part of that decision.
The JSX-structure problem. The community plugin’s rules are about the shape of the JSX and the component definition. jsx-key catches a missing key in an iterator. no-unknown-property catches a misspelled DOM attribute. no-unescaped-entities catches a malformed entity. These are structural correctness rules, not behavioral rules .
a. The eslint-plugin-react setup
The plugin is installed as a dev dependency and imported in the flat config. The recommended config enables the rules that the plugin’s maintainers consider the baseline.
npm install --save-dev eslint-plugin-react
// eslint.config.js
import react from 'eslint-plugin-react';
export default [
{
files: ['**/*.{js,jsx,ts,tsx}'],
...react.configs.recommended,
},
];
The react.configs.recommended object includes the plugin registration and the recommended rules. The files key scopes the config to the React files. The recommended set includes react/jsx-key, react/no-unescaped-entities, react/no-unknown-property, and react/display-name, among others .
A project that uses the new JSX transform (React 17+) should disable react/react-in-jsx-scope. The rule is in the recommended set for the classic transform, where React must be in scope. The new transform imports the JSX factory automatically, so the rule produces false positives.
{
rules: {
'react/react-in-jsx-scope': 'off',
},
}
b. The eslint-plugin-react-hooks setup
The hooks plugin is installed separately and imported in the config. The recommended config is the one that the React team maintains and the one that the compiler uses.
npm install --save-dev eslint-plugin-react-hooks
// eslint.config.js
import reactHooks from 'eslint-plugin-react-hooks';
export default [
reactHooks.configs.flat.recommended,
];
The configs.flat.recommended object is the flat-config form. The legacy form is plugin:react-hooks/recommended. The recommended set includes the two rules that every React project needs: rules-of-hooks and exhaustive-deps .
rules-of-hooks enforces the Rules of Hooks: hooks must be called at the top level of a component or a custom hook, not inside a condition, a loop, or a nested function. A violation is a runtime error, not a style issue. The rule catches the violation at build time .
exhaustive-deps validates that the dependency array of useEffect, useMemo, useCallback, and the other hooks contains every value that the hook references. A missing dependency produces a stale closure, which is one of the most common React bugs .
The recommended config also includes the newer compiler-related rules: immutability, purity, refs, set-state-in-effect, set-state-in-render, static-components, and unsupported-syntax . These rules are the ones that the React Compiler uses to decide whether a component is safe to optimize. A project that has not adopted the compiler can still use the rules to catch the patterns that would block optimization later.
The exhaustive-deps rule has an additionalHooks option for custom hooks that take a dependency array. The React team recommends using it sparingly, because most custom hooks should not expose a dependency array at all .
c. The combined configuration
A React project usually applies both plugins. The community plugin is scoped to the JSX files, and the hooks plugin is applied to the same files. The order matters: the hooks config should come after the community config so that its rules override any conflicting settings.
// eslint.config.js
import js from '@eslint/js';
import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
export default [
js.configs.recommended,
{
files: ['**/*.{js,jsx,ts,tsx}'],
...react.configs.recommended,
rules: {
...react.configs.recommended.rules,
'react/react-in-jsx-scope': 'off',
},
},
{
files: ['**/*.{js,jsx,ts,tsx}'],
plugins: { 'react-hooks': reactHooks },
rules: {
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
},
},
];
The react-hooks rules are referenced with the plugin namespace react-hooks/. The rules-of-hooks rule is an error because a violation is a runtime bug. The exhaustive-deps rule is a warning because the fix is sometimes a judgment call: adding a dependency can cause an infinite loop if the dependency is a new object on every render.
Complete Example Session
// ============================================
// PART 1: THE REACT PLUGIN INSTALL
// ============================================
// npm install --save-dev eslint-plugin-react
// ============================================
// PART 2: THE REACT RECOMMENDED CONFIG
// ============================================
import react from 'eslint-plugin-react';
export default [
{
files: ['**/*.{js,jsx,ts,tsx}'],
...react.configs.recommended,
},
];
// ============================================
// PART 3: THE JSX TRANSFORM OVERRIDE
// ============================================
{
rules: {
'react/react-in-jsx-scope': 'off',
},
}
// ============================================
// PART 4: THE HOOKS PLUGIN INSTALL
// ============================================
// npm install --save-dev eslint-plugin-react-hooks
// ============================================
// PART 5: THE HOOKS RECOMMENDED CONFIG
// ============================================
import reactHooks from 'eslint-plugin-react-hooks';
export default [
reactHooks.configs.flat.recommended,
];
// ============================================
// PART 6: THE MANUAL HOOKS CONFIG
// ============================================
export default [
{
files: ['**/*.{js,jsx}'],
plugins: { 'react-hooks': reactHooks },
rules: {
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
},
},
];
// ============================================
// PART 7: THE COMBINED CONFIG
// ============================================
import js from '@eslint/js';
import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
export default [
js.configs.recommended,
{ files: ['**/*.{js,jsx,ts,tsx}'], ...react.configs.recommended },
{ files: ['**/*.{js,jsx,ts,tsx}'], ...reactHooks.configs.flat.recommended },
];
// ============================================
// PART 8: THE EXHAUSTIVE-DEPS OPTION
// ============================================
{
rules: {
'react-hooks/exhaustive-deps': ['warn', {
additionalHooks: '(useMyCustomHook)',
}],
},
}
// ============================================
// PART 9: THE JSX-KEY RULE
// ============================================
// The community plugin catches the missing key:
items.map((item) => <li>{item.name}</li>)
// react/jsx-key reports the violation.
// ============================================
// PART 10: THE DECISION
// ============================================
// JSX structure → eslint-plugin-react
// Rules of React → eslint-plugin-react-hooks
// Both → install both, apply both
The ten parts covered the React plugin install, the recommended config, the JSX transform override, the hooks plugin install, the hooks recommended config, the manual hooks config, the combined config, the exhaustive-deps option, the jsx-key rule, and the decision.
Quick Reference
The two plugins
| Plugin | Maintainer | Scope |
|---|---|---|
eslint-plugin-react | Community (jsx-eslint) | JSX syntax, component structure |
eslint-plugin-react-hooks | React team | Rules of React, compiler constraints |
The recommended configs
| Plugin | Flat config |
|---|---|
eslint-plugin-react | react.configs.recommended |
eslint-plugin-react-hooks | reactHooks.configs.flat.recommended |
The hooks rules
| Rule | Severity | Purpose |
|---|---|---|
rules-of-hooks | error | Hooks at the top level only |
exhaustive-deps | warn | Complete dependency arrays |
immutability | error | No mutation of props/state |
purity | error | No impure calls during render |
set-state-in-render | error | No setState during render |
The React plugin rules
| Rule | Purpose |
|---|---|
jsx-key | Missing key in iterators |
no-unescaped-entities | Malformed HTML entities |
no-unknown-property | Misspelled DOM attributes |
react-in-jsx-scope | Disable for the new transform |
Best Practices
✅ Do This:
// Use the recommended configs for both plugins
...react.configs.recommended // ✅
...reactHooks.configs.flat.recommended // ✅
// Disable react-in-jsx-scope for the new transform
'react/react-in-jsx-scope': 'off' // ✅
// Keep rules-of-hooks as an error
'react-hooks/rules-of-hooks': 'error' // ✅
// Keep exhaustive-deps as a warning
'react-hooks/exhaustive-deps': 'warn' // ✅
❌ Don’t Do This:
// Use the legacy string config in the flat config
extends: ['plugin:react-hooks/recommended'] // ❌
// Enable react-in-jsx-scope with the new transform
'react/react-in-jsx-scope': 'error' // ❌
// Set exhaustive-deps to off to silence the warnings
'react-hooks/exhaustive-deps': 'off' // ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
react/react-in-jsx-scope false positive | New JSX transform | Disable the rule |
| Hooks rules not applied | The plugin is not installed | Install eslint-plugin-react-hooks |
exhaustive-deps is too noisy | The rule is a warning | Review each warning; add the dependency or refactor |
Missing key is not caught | The React plugin is not applied | Install eslint-plugin-react |
| Compiler skips a component | A hooks rule is violated | Fix the immutability or purity violation |
Real-World Examples
1. React plugin install
npm install --save-dev eslint-plugin-react
2. React recommended config
...react.configs.recommended
3. Hooks plugin install
npm install --save-dev eslint-plugin-react-hooks
4. Hooks recommended config
reactHooks.configs.flat.recommended
5. The rules-of-hooks rule
'react-hooks/rules-of-hooks': 'error'
6. The exhaustive-deps rule
'react-hooks/exhaustive-deps': 'warn'
7. The JSX scope override
'react/react-in-jsx-scope': 'off'
8. The jsx-key rule
'react/jsx-key': 'error'
9. The additionalHooks option
additionalHooks: '(useMyCustomHook)'
10. The decision
JSX structure → eslint-plugin-react; Rules of React → eslint-plugin-react-hooks
Visual
The two plugins
┌─────────────────────────────────────────────────────────────┐
│ eslint-plugin-react │
│ ├── jsx-key │
│ ├── no-unescaped-entities │
│ ├── no-unknown-property │
│ └── display-name │
│ │
│ eslint-plugin-react-hooks │
│ ├── rules-of-hooks │
│ ├── exhaustive-deps │
│ ├── immutability │
│ ├── purity │
│ └── set-state-in-render │
└─────────────────────────────────────────────────────────────┘
The hooks rule timing
┌─────────────────────────────────────────────────────────────┐
│ rules-of-hooks │
│ → build time: catches the violation │
│ → runtime: would throw "Invalid hook call" │
│ │
│ exhaustive-deps │
│ → build time: warns about the missing dependency │
│ → runtime: would produce a stale closure │
└─────────────────────────────────────────────────────────────┘
The compiler connection
┌─────────────────────────────────────────────────────────────┐
│ React Compiler │
│ ├── reads the hooks rules │
│ ├── skips components with violations │
│ └── optimizes the rest │
│ │
│ Fixing the hooks violations increases the │
│ number of optimized components. │
└─────────────────────────────────────────────────────────────┘
The combined config
┌─────────────────────────────────────────────────────────────┐
│ js.configs.recommended │
│ │ │
│ ▼ │
│ react.configs.recommended (JSX structure) │
│ │ │
│ ▼ │
│ reactHooks.configs.flat.recommended (Rules of React) │
└─────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| JSX/structure plugin | eslint-plugin-react |
| Hooks/behavior plugin | eslint-plugin-react-hooks |
| React recommended config | react.configs.recommended |
| Hooks recommended config | reactHooks.configs.flat.recommended |
| Rules of Hooks | rules-of-hooks (error) |
| Dependency arrays | exhaustive-deps (warn) |
| New transform override | react/react-in-jsx-scope: off |
| Compiler rules | immutability, purity, set-state-in-render |
Key takeaways:
- The two plugins have different scopes and different maintainers.
eslint-plugin-reactis the community plugin for JSX syntax and component structure.eslint-plugin-react-hooksis the React team’s plugin for the Rules of React. A React project needs both . - The hooks plugin’s
recommendedconfig is the one the React Compiler uses. The rules likeimmutability,purity, andset-state-in-renderare the patterns that the compiler checks before it optimizes a component. Fixing those violations increases the number of components the compiler can safely optimize . rules-of-hooksis an error;exhaustive-depsis a warning. A hooks ordering violation is a runtime error. A missing dependency is a potential stale closure, but the fix is sometimes a judgment call. The React team’s recommended config uses error for the first and warn for the second .react/react-in-jsx-scopemust be disabled for the new JSX transform. The rule is in the community plugin’s recommended set for the classic transform, where React must be in scope. The new transform imports the JSX factory automatically, so the rule produces false positives.- The
exhaustive-depsrule has anadditionalHooksoption. It is for custom hooks that take a dependency array. The React team recommends using it sparingly, because most custom hooks should not expose a dependency array at all . - The flat config uses the plugin’s config object. The community plugin’s
recommendedconfig is an object that is spread into the array. The hooks plugin’s flat config isconfigs.flat.recommended. The legacy string form is for the older.eslintrcformat.
Remember: React linting is two plugins, not one. The community plugin catches the JSX and component-structure issues: missing keys, unknown properties, unescaped entities. The React team’s plugin catches the behavioral rules: hooks ordering, dependency arrays, purity, and immutability. The hooks plugin’s recommended config is the one that the React Compiler uses to decide which components are safe to optimize. Install both, apply both, and disable the react-in-jsx-scope rule if the project uses the new transform.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!