facebook/reactMITd083ec1Report / request removal

ESLint Plugin: react-hooks

This package provides two static checks: rules-of-hooks enforces where Hooks may be called, while exhaustive-deps checks dependency arrays for Hooks such as useEffect, useMemo, and useCallback.

It exists because Hook correctness depends on source-level structure: Hooks must execute in a consistent order, and reactive callbacks must account for values that can change between renders.

Sources: packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts:304-306

Core concepts

Hook recognition

A Hook is recognized by a name equal to use or matching use[A-Z0-9]; member expressions are also accepted when the namespace begins with an uppercase letter.

Sources: packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts:29-31, packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts:37-51

Component or Hook scope

A function is treated as component-or-Hook code when its name is component-like, Hook-like, or when it is a callback passed to forwardRef or memo.

Sources: packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts:98-112

Reactive Hook

A reactive Hook is a built-in Hook such as useEffect, useLayoutEffect, useCallback, or useMemo, useImperativeHandle, or a configured custom Hook matched by additionalHooks.

Sources: packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts:1990-2033

Declared dependency

A declared dependency is represented by DeclaredDependency, which stores a dependency key and its AST node.

Sources: packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts:26-29

Inferred dependency

An inferred dependency is represented by Dependency, which records whether the value is stable and all references that use it.

Sources: packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts:31-34

Dependency tree

A DependencyTreeNode is a property-path tree whose nodes record used values, satisfied dependency declarations, deeper usage, and child properties.

Sources: packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts:36-41

How rules-of-hooks detects conditional calls

The rule first records possible Hook calls by testing each call callee with isHook. It stores each Hook under the current code-path segment instead of deciding immediately, because the final answer depends on all paths through the function.

The rule identifies valid context by walking ancestors with isInsideComponentOrHook. It uses getFunctionName, isComponentName, and isHook, while also accepting forwardRef and memo callbacks.

At the end of a code path, onCodePathEnd evaluates whether every recorded Hook is reachable from the start and from every final segment. It computes paths with countPathsFromStart, countPathsToEnd, and shortestPathLengthToStart, caching results and marking cycles while traversing predecessor and successor segments.

This is how a condition or loop is detected statically: a Hook placed in only one branch does not have the same path coverage as a Hook that appears on every route, while a cycle is explicitly recorded rather than treated as an ordinary straight-line path.

Hook path analysis — How does the rule detect a conditional or looped Hook?

Evidence

Sources: packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts:756-799, packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts:98-112, packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts:304-306, packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts:338-388, packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts:412-455, packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts:479-512

How exhaustive-deps builds dependency recommendations

visitCallExpression classifies a call with getReactiveHookCallbackIndex. Built-in reactive Hooks map their callback to argument position zero, useImperativeHandle maps it to position one, and configured custom Hooks can match additionalHooks.

For an inline function callback, visitCallExpression calls visitFunctionWithDependencies. That function acquires the callback scope, finds the enclosing pure scopes, and treats variables declared there as values that may need to be listed.

gatherDependenciesRecursively walks references in the callback and child scopes. It ignores unresolved references and values outside pure scopes, finds the relevant AST node with fastFindReferenceWithParent, normalizes it with getDependency, and converts property access into a string path with analyzePropertyChain.

Property access is therefore preserved as a path such as props.foo.id, including optional-chain information. analyzePropertyChain recursively combines non-computed member names, while markNode records whether each path was optional or required. getDependency can widen a member expression to its containing object, except for .current, computed access, and certain call shapes.

Each dependency is stored with a stability decision. isStableKnownHookValue recognizes primitive constants and stable Hook results such as useRef, while isFunctionWithoutCapturedValues accepts a component-scope function when it captures no changing pure-scope value. The rule memoizes these decisions with memoizeWithWeakMap.

Value categoryDependency treatment
Primitive const literalConsidered stable
useRef resultConsidered stable
Setter or dispatcher from useState, useReducer, or useActionStateStable tuple value
Function without changing capturesConsidered stable
Other render-scope valueTracked as a dependency

collectRecommendations compares inferred dependencies with declared dependencies by building a DependencyTreeNode hierarchy. It marks used paths, marks declared and stable paths as satisfied, and then scans the tree for missing and satisfying paths.

A declared parent can satisfy deeper property usage: the scan treats props.foo as sufficient for props.foo.id because the parent node is satisfied recursively. The rule formats optional paths with formatDependency, reports missing names through getWarningMessage, and can replace the dependency array with formatted suggestions.

Dependency inference — How does a callback become a dependency-array recommendation?

Evidence

The rule also warns about writes that occur inside a Hook callback because those assignments are lost after each render; reportStaleAssignment recommends moving the value into the Hook or storing mutable state in useRef. It can identify constructions such as objects, arrays, functions, classes, JSX, and regular expressions and offer wrapping fixes when those values cause unstable dependencies.

Sources: packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts:1326-1526, packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts:1990-2033, packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts:181-1324, packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts:505-595, packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts:1927-1968, packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts:1901-1918, packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts:1865-1893, packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts:266-403, packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts:406-461, packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts:163-177, packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts:576-589, packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts:1535-1710, packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts:1631-1663, packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts:1021-1033, packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts:1314-1320, packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts:643-658, packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts:1714-1763

How it connects

The package entry point imports ExhaustiveDeps, RulesOfHooks, and shared React Compiler rule configuration, so this lint package sits beside compiler-related lint integration rather than replacing it.

The separate react-compiler ESLint rule obtains a compiler result and turns compiler events into lint diagnostics. The supplied excerpts do not show eslint-plugin-react-hooks calling the React Compiler directly; they show why compiler integration exists: compiler validation also rejects conditional Hook calls, and compiler dependency validation infers dependencies for compilation.

Sources: packages/eslint-plugin-react-hooks/src/index.ts:7-16, compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts:175-182, compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts:102-114, compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateExhaustiveDependencies.ts:495-519

Key takeaways

  • rules-of-hooks records Hook calls by code-path segment and compares reachability across branches and cycles.
  • exhaustive-deps gathers resolved references from pure scopes, normalizes property paths, and classifies stable values.
  • A dependency tree lets a declared parent satisfy deeper property reads.
  • The package is adjacent to compiler linting, but the shown code does not prove that these rules call the compiler directly.

Want this for your repos?

Try Angada AI Wiki