diff --git a/packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js b/packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js index 29e956d314a2..b375bd572698 100644 --- a/packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js +++ b/packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js @@ -8009,6 +8009,21 @@ const testsTypescript = { } `, }, + { + code: normalizeIndent` + function MyComponent() { + const [foo, setFoo] = React.useState<{bar: number}>({bar: 42}); + + useEffect(() => { + const square = (x: typeof foo.bar) => x * x; + setFoo(previous => ({ + ...previous, + bar: square(previous.bar), + })); + }, []); + } + `, + }, { code: normalizeIndent` function MyComponent() { diff --git a/packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts b/packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts index 6b790680608d..11f2157941bd 100644 --- a/packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts +++ b/packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts @@ -522,6 +522,9 @@ const rule = { if (referenceNode == null) { continue; } + if (isInsideTypeQueryOrReference(referenceNode)) { + continue; + } const dependencyNode = getDependency(referenceNode); const dependency = analyzePropertyChain( dependencyNode, @@ -1892,6 +1895,20 @@ function getDependency(node: Node): Node { } } +function isInsideTypeQueryOrReference(node: Node): boolean { + let current: Node | null = node.parent ?? null; + while (current != null) { + if ( + current.type === 'TSTypeQuery' || + current.type === 'TSTypeReference' + ) { + return true; + } + current = current.parent ?? null; + } + return false; +} + /** * Mark a node as either optional or required. * Note: If the node argument is an OptionalMemberExpression, it doesn't necessarily mean it is optional.