What's Changed
๐ฅ 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-iife
| unsupported-syntax
| consolidated |
jsx-no-undef
| โ | 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-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.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 |
|---|---|---|---|
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
| |
filename
| 2.13.0 | eslint-plugin-react-naming-convention
| |
filename-extension
| 2.13.0 | eslint-plugin-react-naming-convention
|
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 |
Preset changes
| Change | Rule | Presets affected | Severity / Notes |
|---|---|---|---|
| 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 | @eslint-react/hooks-extra/*
| recommended, all (in @eslint-react/eslint-plugin)
| removed |
| 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/use-memo
| recommended, x
| error
|
| Moved | react-naming-convention/use-state โ react-x/use-state
| recommended, x
| warn
|
| 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
|
| 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/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. Contributed by @Rel1cx in #1506exhaustive-deps: Enforces that React hook dependency arrays contain all reactive values used in the callback. Contributed by @TrevorBurnham in #1499immutability(Experimental): Validates against mutating props, state, and other values that are immutable. Detects in-place array mutations (e.g.,push,sort,splice) and direct property assignments on state variables fromuseStateand props objects. Mirrors theimmutabilitylint rule described in the React docs.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. Contributed by @TrevorBurnham in #1499set-state-in-effect: Validates against callingsetStatesynchronously in an effect, which can lead to re-renders that degrade performance. Contributed by @Rel1cxCompared to the old
no-direct-set-state-in-use-effectrule, the newset-state-in-effectrule allows setting state in effects if the value is from a ref. All other behaviors remain the same.The following example from the React Docs demonstrates that
setStatein 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); }, []); // ... }
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. Mirrors theuse-memolint rule described in the React docs.use-state: Enforces correct usage of theuseStatehook โ destructuring, symmetric naming of the value and setter (e.g.,count/setCount), and lazy initialization of expensive initial state. Moved fromeslint-plugin-react-naming-conventionintoeslint-plugin-react-xso that all hook-usage rules live in one package. The lazy-initialization behavior (previouslyprefer-use-state-lazy-initialization) is now also part of this rule and controlled by theenforceLazyInitializationoption (default:true).set-state-in-render(Experimental): Validates against unconditionally setting state during render, which can trigger additional renders and potential infinite render loops. Contributed by @Rel1cx in #1501unsupported-syntax: Validates against syntax that React Compiler does not support, includingeval,withstatements, and IIFEs in JSX (previously covered byjsx-no-iife).
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 picked up by the compiler. Accepted values are "off", "infer", "annotation", "syntax", and "all" (default: "annotation"). When set to anything other than "off", the compiler is considered enabled.
๐ช Improvements
- Adopted
tslfor type checking across the monorepo, improving linting performance. Contributed by @Rel1cx in #1532 - Directly ported
eslint-plugin-react-hooksrules (rules-of-hooksandexhaustive-deps) with code-path analysis for more accurate hook validation. Contributed by @Rel1cx in #1535 - Improved
set-state-in-effectrule to allowsetStatecalls when the new state is derived from refs (aligning with React's recommended patterns). Contributed by @Rel1cx in #1521 - Improved detection of React components created via conditional (ternary) expressions (e.g.,
const Component = condition ? () => <A/> : () => <B/>) infunction-componentandno-nested-component-definitionsrules. Contributed by @Rel1cx in #1503 - Refactored core modules to use
defineRuleListenerhelper for more consistent rule listener definitions across all rules. Contributed by @Rel1cx in #1517 - Restructured and consolidated component detection modules for better maintainability and accuracy. Contributed by @Rel1cx
- Restructured core, var, and ast utility modules for improved code organization. Contributed by @Rel1cx in #1520
- Updated documentation with standardized "See Also" sections and unified "Further Reading" sections across all rule documentation. Contributed by @Rel1cx in #1536 and #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
- 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 any
@eslint-react/hooks-extra/*rules from your config โ these have been removed from@eslint-react/eslint-plugin. - 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-iifefrom your config if present โ this rule has been consolidated intoreact-x/unsupported-syntax. - 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/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/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.
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/purity(warn) โ validates that components and hooks are pure by checking that they do not call known-impure functions 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
ESLint React now provides direct equivalents for the core rules in eslint-plugin-react-hooks, including both the classic hooks linting rules (exhaustive-deps, rules-of-hooks) and many of the newer React Compiler diagnostic rules.
eslint-plugin-react-hooks Rule
| ESLint React Equivalent | Status |
|---|---|---|
exhaustive-deps
| react-x/exhaustive-deps
| ๐ง Auto-fixable |
rules-of-hooks
| react-x/rules-of-hooks
| ๐ง Auto-fixable |
component-hook-factories
| react-x/component-hook-factories
| โ Supported |
error-boundaries
| react-x/error-boundaries
| โ Supported |
immutability
| react-x/immutability
| ๐ก ๐งช Experimental |
purity
| react-x/purity
| ๐ก ๐งช Experimental |
refs
| react-x/refs
| ๐ก ๐งช Experimental |
set-state-in-effect
| react-x/set-state-in-effect
| โ Supported |
set-state-in-render
| react-x/set-state-in-render
| ๐งช Experimental |
static-components
| react-x/no-nested-component-definitions
| โ Supported |
unsupported-syntax
| react-x/unsupported-syntax
| โ Supported |
use-memo
| react-x/use-memo
| โ Supported |
Legend: ๐ง Fully supported with auto-fix โ โ Mostly supported โ ๐ก Partial support โ ๐งช Experimental
Note: React Compiler-specific rules (config, gating, globals, incompatible-library, preserve-manual-memoization) do not have equivalents in ESLint React, as they validate compiler-specific configuration and behavior.
See the complete migration guide for more details.
Full Changelog: v2.13.0...v3.0.0-beta.33