What's Changed
This entry consolidates all changes from v2.13.0 (2026-02-15) through v3.0.0-rc.0 (2026-03-08).
💥 Breaking Changes
Minimum Node.js version raised from 20 to 22
The minimum required Node.js version is now >=22.0.0 (previously >=20.19.0).
Consolidated and removed rules in eslint-plugin-react-x
Old Rule (react-x/)
| New Rule (react-x/)
| Change |
|---|---|---|
jsx-no-duplicate-props
| — | removed |
jsx-no-iife
| unsupported-syntax
| consolidated |
jsx-no-undef
| — | removed |
jsx-uses-react
| — | removed |
jsx-uses-vars
| — | removed |
no-unnecessary-key
| — | removed |
no-useless-forward-ref
| no-forward-ref
| consolidated |
prefer-read-only-props
| immutability
| consolidated |
prefer-use-state-lazy-initialization
| use-state
| consolidated |
jsx-no-duplicate-props: Removed. LSP and Language Features natively report duplicate JSX props, making this rule redundant.jsx-no-iife: The IIFE-in-JSX check has been merged into the newunsupported-syntaxrule, which also coversevalandwithstatements.jsx-no-undef: ESLint v10.0.0 now tracks JSX references natively, making this rule redundant. Theno-undefrule now correctly handles JSX element references.jsx-uses-reactandjsx-uses-vars: These rules were only necessary for older versions of React that required theReactnamespace in scope for JSX. Since React 17, this is no longer required, and ESLint v10.0.0's native variable tracking handles this correctly.no-unnecessary-key: The experimental rule has been removed.no-useless-forward-ref: Consolidated intono-forward-ref. Since React 19,forwardRefis no longer necessary asrefcan be passed as a prop. Theno-forward-refrule now covers allforwardRefusage patterns.prefer-read-only-props: The TypeScript-based read-only props enforcement has been consolidated into the newimmutabilityrule, which covers a broader set of immutability violations, including in-place array mutations and direct property assignments on state and props.prefer-use-state-lazy-initialization: Its lazy-initialization checks are now part of theuse-staterule and controlled by the newenforceLazyInitializationoption (default:true).
Removed previously deprecated rules
| Rule | Deprecated in | Package | Replacement |
|---|---|---|---|
filename-extension
| 2.13.0 | eslint-plugin-react-naming-convention
| |
filename
| 2.13.0 | eslint-plugin-react-naming-convention
| |
no-default-props
| 2.9.3 | eslint-plugin-react-x
| no-restricted-syntax
|
no-forbidden-props
| 2.3.2 | eslint-plugin-react-x
| no-restricted-syntax
|
no-prop-types
| 2.9.3 | eslint-plugin-react-x
| no-restricted-syntax
|
no-string-refs
| 2.9.3 | eslint-plugin-react-x
| no-restricted-syntax
|
no-unnecessary-use-ref
| 2.10.0 | eslint-plugin-react-x
|
Removed eslint-plugin-react-hooks-extra package
All rules have been migrated into eslint-plugin-react-x:
Old Rule (react-hooks-extra/)
| New Rule (react-x/)
| Change |
|---|---|---|
no-direct-set-state-in-use-effect
| set-state-in-effect
| relocated, renamed |
Removed naming-convention/component-name rule (#1557)
Removed unneeded APIs from @eslint-react/core (#1556)
Restructured packages and removed deprecated rules for ESLint v10+ (#1592)
Preset changes
| Change | Rule | Presets affected | Severity / Notes |
|---|---|---|---|
| Added | react-x/component-hook-factories
| recommended, x
| error
|
| Added | react-x/error-boundaries
| recommended, x
| error
|
| Added | react-x/exhaustive-deps
| recommended, x
| warn
|
| Added | react-x/immutability
| all
| error
|
| Added | react-x/no-unused-class-component-members
| recommended, x
| warn
|
| Added | react-x/purity
| recommended, x
| warn
|
| Added | react-x/refs
| all
| error
|
| Added | react-x/rules-of-hooks
| recommended, x
| error
|
| Added | react-x/set-state-in-effect
| recommended, x
| warn
|
| Added | react-x/set-state-in-render
| recommended, x
| error
|
| Added | react-x/unsupported-syntax
| recommended, x
| error
|
| Added | react-x/use-memo
| recommended, x
| error
|
| Moved | react-naming-convention/use-state → react-x/use-state
| recommended, x
| warn
|
| Removed | @eslint-react/hooks-extra/*
| recommended, all (in @eslint-react/eslint-plugin)
| removed |
| Removed | react-naming-convention/component-name
| all
| removed |
| Removed | react-naming-convention/use-state
| all
| moved to react-x/use-state
|
| Removed | react-x/jsx-no-duplicate-props
| recommended, x, all
| LSP and Language Features natively report duplicate JSX props |
| Removed | react-x/jsx-no-iife
| strict, disable-experimental, all
| merged into unsupported-syntax
|
| Removed | react-x/jsx-no-undef
| all
| ESLint v10.0.0+ native support |
| Removed | react-x/jsx-uses-react
| recommended, x, all
| ESLint v10.0.0+ native support |
| Removed | react-x/jsx-uses-vars
| recommended, x, all
| ESLint v10.0.0+ native support |
| Removed | react-x/no-default-props
| recommended, x
| removed |
| Removed | react-x/no-prop-types
| recommended, x
| removed |
| Removed | react-x/no-string-refs
| recommended, x
| removed |
| Removed | react-x/no-unnecessary-key
| all
| removed |
| Removed | react-x/no-useless-forward-ref
| recommended, x, all
| merged into no-forward-ref
|
| Removed | react-x/prefer-read-only-props
| disable-experimental, disable-type-checked
| consolidated into immutability
|
| Removed | react-x/prefer-use-state-lazy-initialization
| recommended, x, all
| merged into use-state
|
✨ New
Added the following new rules to eslint-plugin-react-x:
component-hook-factories: Validates against higher-order functions defining nested components or hooks. Components and hooks should be defined at the module level.error-boundaries: Validates usage of Error Boundaries instead of try/catch for errors in child components. Try/catch blocks can't catch errors during React's rendering process — only Error Boundaries can catch these errors. (#1506)exhaustive-deps: Enforces that React hook dependency arrays contain all reactive values used in the callback. (#1499)immutability(Experimental): Validates against mutating props, state, and other values that are immutable. Detects in-place array mutations (ex:push,sort,splice) and direct property assignments on state variables fromuseStateand props objects. Mirrors theimmutabilitylint rule described in the React docs.no-implicit-childrenandno-implicit-ref: New rules added in beta.75. (#1594)no-mixing-controlled-and-uncontrolled→unstable-rules-of-props: Validates against mixing controlled and uncontrolled prop patterns. (#486, #1571)purity(Experimental): Validates that components and hooks are pure by checking that they do not call known-impure functions during render.refs(Experimental): Validates correct usage of refs by checking thatref.currentis not read or written during render. See the "pitfalls" section inuseRef().rules-of-hooks: Enforces the Rules of Hooks. (#1499)set-state-in-effect: Validates against callingsetStatesynchronously in an effect, which can lead to re-renders that degrade performance.Compared to the old
no-direct-set-state-in-use-effectrule, the following example from the React Docs demonstrates thatsetStatein an effect is fine if the value comes from a ref, since it cannot be calculated during rendering:import { useLayoutEffect, useRef, useState } from "react"; function Tooltip() { const ref = useRef(null); const [tooltipHeight, setTooltipHeight] = useState(0); useLayoutEffect(() => { const { height } = ref.current.getBoundingClientRect(); setTooltipHeight(height); }, []); // ... }
set-state-in-render(Experimental): Validates against unconditionally setting state during render, which can trigger additional renders and potential infinite render loops. (#1501)unsupported-syntax: Validates against syntax that React Compiler does not support, includingeval,withstatements, and IIFEs in JSX (previously covered byjsx-no-iife).use-memo: Validates thatuseMemois called with a callback that returns a value.useMemois designed for computing and caching values — without a return value it always returnsundefined, which defeats its purpose. This rule also catchesuseMemocalls whose return value is discarded (not assigned to a variable), which indicates a side-effect misuse that should useuseEffectinstead.use-state: Enforces correct usage of theuseStatehook — destructuring, symmetric naming of the value and setter (ex:count/setCount), and lazy initialization of expensive initial state. Moved fromeslint-plugin-react-naming-conventionintoeslint-plugin-react-x. The lazy-initialization behavior (previouslyprefer-use-state-lazy-initialization) is controlled by theenforceLazyInitializationoption (default:true).
Added compilationMode setting:
Added support for the compilationMode setting under settings["react-x"]. This setting informs rules about the React Compiler compilation mode the project is using, allowing rules to understand how components and hooks will be optimized by the compiler.
Added disable-conflict-eslint-plugin-react-hooks configuration to @eslint-react/eslint-plugin for easier migration from eslint-plugin-react-hooks.
Added unstable-rules-of-props support for generic foo/defaultFoo prop pairs (#1580)
🐞 Fixes
- Enable
immutabilityrule in theallconfiguration preset - Fix MDX documentation formatting issues (#1559)
- Fix TS type expressions handling in rules, closes #1583 (#1584)
- Fix
no-forward-ref: do not add ref param when type arguments are mismatched (#1561) - Fix
no-forward-ref: handle callback with no params (#1560) - Fix
no-unused-propsfalse positive for anonymous callbacks passed to call expressions (#1602) - Fix
no-unused-propsfalse positive when using Omit on union props type (#1578) - Fix
no-useless-fragmentfalse positive when passing arefto aFragment, closes #1567 (#1568) - Fix additional
useStatenames should only match patterns configured inadditionalStateHooks(#1601) - Fix object type compute on
Boolean()like calls, closes #1587 (#1591) - Fix the migration guide link 404 in the FAQ, closes #1589 (#1590)
- Fix traverse type expressions in component id, init path, and wrapper (#1603)
ex: components usingsatisfieswere previously not detected:// previously missed — now correctly identified as a component const App = (() => <div />) satisfies React.FC;
🪄 Improvements
- Adopted
tslfor type checking across the monorepo, improving linting performance (#1532) - Apply
additionalStateHooksandadditionalEffectHooksto more rules (#1597) - Bump ESLint to 10.0.3
- Bump dependencies and update workflows
- Bump tsdown target to Node 22
- Changed the following rules from
problemtosuggestiontype for better categorization:react-x/jsx-dollarreact-x/jsx-shorthand-booleanreact-x/jsx-shorthand-fragmentreact-x/no-array-index-keyreact-x/no-children-countreact-x/no-children-for-eachreact-x/no-children-mapreact-x/no-children-onlyreact-x/no-children-propreact-x/no-children-to-arrayreact-x/no-class-componentreact-x/no-clone-elementreact-x/no-create-refreact-x/no-missing-component-display-namereact-x/no-missing-context-display-namereact-x/no-unnecessary-use-callbackreact-x/no-unnecessary-use-memoreact-x/no-unused-class-component-membersreact-x/no-unused-propsreact-x/no-unused-statereact-x/no-useless-fragmentreact-x/prefer-namespace-importreact-dom/prefer-namespace-import
- Converted React 19 migration rules from auto-fix to suggestions (
no-context-provider,no-forward-ref,no-use-context) to prevent automatic code modifications that could break existing code - Enhanced
purityrule with TypeScript type expression support for more accurate detection of impure constructs - Improved
resolvefunction with comprehensive definition types (#1576) - Improved
set-state-in-effectrule to allowsetStatecalls when the new state is derived from refs (aligning with React's recommended patterns) (#1521) - Improved detection of React components created via conditional (ternary) expressions in the
function-componentandno-nested-component-definitionsrules (#1503) - Inlined
getChildScopes,getVariableInitializerand cleaned up var utilities (#1562, #1572) - Moved
@local/efftodevDependenciesacross workspace (#1596) - Moved
disable-conflict-*configs toeslint-plugin-react-x(#1577) - Ported
eslint-plugin-react-hooksrules (rules-of-hooksandexhaustive-deps) verbatim with code-path analysis for more accurate hook validation (#1535) - Refactored core modules to use the
defineRuleListenerhelper for more consistent rule listener definitions (#1517) - Refactored refs rule (#1563)
- Refined JSX attribute value extraction (#1579)
- Removed React 19 version restriction from
context-namerule (#1558) - Removed config adapters and fixed plugin default export types, closes #1564 (#1565)
- Replaced jsx submodule functional APIs with
JsxInspectorclass (#1585) - Restructured and consolidated component-detection modules for better maintainability and accuracy
- Restructured core, var, and ast utility modules for improved code organization (#1520)
- Standardized rule docs and unified MDX heading structure (#1555)
- Updated documentation with standardized "See Also" and "Further Reading" sections (#1536, #1537)
✅ Upgrade Checklist
Use this checklist to upgrade from v2.x to v3.0.0:
Node.js
- Upgrade Node.js to
>=22.0.0(previously>=20.19.0).
Package changes
- Remove
eslint-plugin-react-hooks-extrafrom yourpackage.json— this package is no longer published. - If you were importing
eslint-plugin-react-hooks-extradirectly, replace it witheslint-plugin-react-x.
ESLint configuration
- Remove
@eslint-react/hooks-extra/*rules from your config — these have been removed from@eslint-react/eslint-plugin. - Replace
react-hooks-extra/no-direct-set-state-in-use-effectwithreact-x/set-state-in-effectin your ESLint config. - Replace
react-naming-convention/use-state(or@eslint-react/naming-convention/use-state) withreact-x/use-state(or@eslint-react/use-state) in your ESLint config. - Remove references to the following deleted rules (use
no-restricted-syntaxinstead if needed):react-x/no-default-propsreact-x/no-forbidden-propsreact-x/no-prop-typesreact-x/no-string-refs
- Remove
react-x/jsx-no-duplicate-propsfrom your config if present — this rule has been removed since LSP and Language Features natively report duplicate JSX props. - Remove
react-x/jsx-no-undeffrom your config if present — this rule has been removed since ESLint v10.0.0 now tracks JSX references natively. - Remove
react-x/jsx-uses-reactandreact-x/jsx-uses-varsfrom your config if present — these rules have been removed since ESLint v10.0.0 now handles JSX variable tracking natively. - Remove
react-x/no-unnecessary-keyfrom your config if present — this rule has been deleted with no replacement. - Remove
react-x/no-unnecessary-use-reffrom your config if present — this rule has been deleted with no replacement. - Remove
react-x/jsx-no-iifefrom your config if present — this rule has been consolidated intoreact-x/unsupported-syntax. - Remove
react-x/no-useless-forward-reffrom your config if present — this rule has been consolidated intoreact-x/no-forward-ref. - Remove
react-x/prefer-read-only-propsfrom your config if present — this rule has been consolidated intoreact-x/immutability. - Remove
react-x/prefer-use-state-lazy-initializationfrom your config if present — this rule has been consolidated intoreact-x/use-stateand its lazy-initialization checks are now controlled by theenforceLazyInitializationoption. - Remove
react-naming-convention/filenameandreact-naming-convention/filename-extensionfrom your config if present — these rules have been deleted with no replacement. - Remove
react-naming-convention/component-namefrom your config if present — this rule has been deleted with no replacement. - Be aware that
react-x/no-context-provider,react-x/no-forward-ref, andreact-x/no-use-contextno longer auto-fix via--fix— their fixes have been converted to suggestions to prevent automatic code modifications that could break existing code. If you relied oneslint --fixfor these rules, you will now need to apply fixes manually.
Settings
- If your project uses React Compiler, consider adding the
compilationModesetting undersettings["react-x"]to inform rules about the compilation mode your project is using (possible values:"infer","annotation","syntax","all").
Review new rules enabled in presets
If you use the recommended, x, or all preset, the following rules are now included automatically. Review your codebase for new reports:
-
react-x/component-hook-factories(error) — catches factory functions that define components or hooks inside them instead of at the module level. -
react-x/error-boundaries(error) — catches try/catch blocks wrapping JSX orusehook calls where Error Boundaries should be used instead. -
react-x/exhaustive-deps(warn) — enforces that hook dependency arrays contain all reactive values. -
react-x/immutability(error,allonly) — validates against mutating props, state, and other immutable values, including in-place array mutations and direct property assignments. -
react-x/no-unused-class-component-members(warn) — catches unused class component methods and properties. -
react-x/purity(warn) — validates that components and hooks are pure by checking that they do not call known-impure functions during render. -
react-x/refs(error,allonly) — validates correct usage of refs by checking thatref.currentis not read or written during render. -
react-x/rules-of-hooks(error) — enforces the Rules of Hooks. -
react-x/set-state-in-effect(warn) — catches synchronoussetStatecalls inside effects. -
react-x/set-state-in-render(error) — catches unconditionalsetStatecalls during render that can cause infinite loops. -
react-x/unsupported-syntax(error) — catches usage of syntax that React Compiler does not support, includingeval,withstatements, and IIFEs in JSX. -
react-x/use-memo(error) — catchesuseMemocalls where the callback has no return value or where theuseMemoreturn value is discarded. -
react-x/use-state(warn) — Enforces correct usage ofuseState, including destructuring, symmetric naming of the value and setter (previouslyreact-naming-convention/use-state), and wrapping expensive initializers in a lazy initializer function (previouslyprefer-use-state-lazy-initialization).
📘 Migration Guide: From eslint-plugin-react-hooks
See the complete migration guide for more details.
Full Changelog: v2.13.0...v3.0.0-rc.0
New Contributors
- @TrevorBurnham made their first contribution in #1499
- @gmurphey made their first contribution in #1578
Full Changelog: v2.13.0...v3.0.0-rc.0