From c6cfbf03120790ae4d91d5686a7f6423bb9f4617 Mon Sep 17 00:00:00 2001 From: Ali Motahari Date: Wed, 1 Oct 2025 06:22:21 +0330 Subject: [PATCH 1/2] fix: allow useQueries with combine property in no-unstable-deps rule - Add hasCombineProperty function to detect combine property in useQueries calls - Skip tracking useQueries with combine as unstable since combine makes result stable - Add tests for useQueries with and without combine property - Fixes #9718 --- .../src/__tests__/no-unstable-deps.test.ts | 47 ++++++++++++++++++- .../no-unstable-deps/no-unstable-deps.rule.ts | 18 +++++++ 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin-query/src/__tests__/no-unstable-deps.test.ts b/packages/eslint-plugin-query/src/__tests__/no-unstable-deps.test.ts index 32cbf542d7..041046382d 100644 --- a/packages/eslint-plugin-query/src/__tests__/no-unstable-deps.test.ts +++ b/packages/eslint-plugin-query/src/__tests__/no-unstable-deps.test.ts @@ -42,7 +42,26 @@ const baseTestCases = { } `, })), - ), + ).concat([ + { + name: `should pass when useQueries with combine is passed to ${reactHookAlias} as dependency`, + code: ` + ${reactHookImport} + import { useQueries } from "@tanstack/react-query"; + + function Component() { + const queries = useQueries({ + queries: [ + { queryKey: ['test'], queryFn: () => 'test' } + ], + combine: (results) => ({ data: results[0]?.data }) + }); + const callback = ${reactHookInvocation}(() => { queries.data }, [queries]); + return; + } + `, + }, + ]), invalid: ({ reactHookImport, reactHookInvocation, @@ -88,7 +107,31 @@ const baseTestCases = { }, ], })), - ), + ).concat([ + { + name: `result of useQueries without combine is passed to ${reactHookInvocation} as dependency`, + code: ` + ${reactHookImport} + import { useQueries } from "@tanstack/react-query"; + + function Component() { + const queries = useQueries({ + queries: [ + { queryKey: ['test'], queryFn: () => 'test' } + ] + }); + const callback = ${reactHookInvocation}(() => { queries[0]?.data }, [queries]); + return; + } + `, + errors: [ + { + messageId: 'noUnstableDeps', + data: { reactHook: reactHookAlias, queryHook: 'useQueries' }, + }, + ], + }, + ]), } const testCases = (reactHookName: string) => [ diff --git a/packages/eslint-plugin-query/src/rules/no-unstable-deps/no-unstable-deps.rule.ts b/packages/eslint-plugin-query/src/rules/no-unstable-deps/no-unstable-deps.rule.ts index f2a05a2819..eb3e2f9422 100644 --- a/packages/eslint-plugin-query/src/rules/no-unstable-deps/no-unstable-deps.rule.ts +++ b/packages/eslint-plugin-query/src/rules/no-unstable-deps/no-unstable-deps.rule.ts @@ -67,6 +67,19 @@ export const rule = createRule({ } } + function hasCombineProperty(callExpression: TSESTree.CallExpression): boolean { + if (callExpression.arguments.length === 0) return false + + const firstArg = callExpression.arguments[0] + if (!firstArg || firstArg.type !== AST_NODE_TYPES.ObjectExpression) return false + + return firstArg.properties.some(prop => + prop.type === AST_NODE_TYPES.Property && + prop.key.type === AST_NODE_TYPES.Identifier && + prop.key.name === 'combine' + ) + } + return { ImportDeclaration(node: TSESTree.ImportDeclaration) { if ( @@ -94,6 +107,11 @@ export const rule = createRule({ node.init.callee.type === AST_NODE_TYPES.Identifier && allHookNames.includes(node.init.callee.name) ) { + // Special case for useQueries with combine property - it's stable + if (node.init.callee.name === 'useQueries' && hasCombineProperty(node.init)) { + // Don't track useQueries with combine as unstable + return + } collectVariableNames(node.id, node.init.callee.name) } }, From 28e98fac718fdea890dd9e29844d1a217dfb9686 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sat, 4 Oct 2025 19:56:54 +0000 Subject: [PATCH 2/2] ci: apply automated fixes --- .../src/__tests__/no-unstable-deps.test.ts | 72 ++++++++++--------- .../no-unstable-deps/no-unstable-deps.rule.ts | 21 ++++-- 2 files changed, 52 insertions(+), 41 deletions(-) diff --git a/packages/eslint-plugin-query/src/__tests__/no-unstable-deps.test.ts b/packages/eslint-plugin-query/src/__tests__/no-unstable-deps.test.ts index 041046382d..1c97152add 100644 --- a/packages/eslint-plugin-query/src/__tests__/no-unstable-deps.test.ts +++ b/packages/eslint-plugin-query/src/__tests__/no-unstable-deps.test.ts @@ -28,10 +28,11 @@ const baseTestCases = { } `, }, - ].concat( - useQueryHookNames.map((queryHook) => ({ - name: `should pass result of ${queryHook} is passed to ${reactHookInvocation} as dependency`, - code: ` + ] + .concat( + useQueryHookNames.map((queryHook) => ({ + name: `should pass result of ${queryHook} is passed to ${reactHookInvocation} as dependency`, + code: ` ${reactHookImport} import { ${queryHook} } from "@tanstack/react-query"; @@ -41,11 +42,12 @@ const baseTestCases = { return; } `, - })), - ).concat([ - { - name: `should pass when useQueries with combine is passed to ${reactHookAlias} as dependency`, - code: ` + })), + ) + .concat([ + { + name: `should pass when useQueries with combine is passed to ${reactHookAlias} as dependency`, + code: ` ${reactHookImport} import { useQueries } from "@tanstack/react-query"; @@ -60,8 +62,8 @@ const baseTestCases = { return; } `, - }, - ]), + }, + ]), invalid: ({ reactHookImport, reactHookInvocation, @@ -87,10 +89,11 @@ const baseTestCases = { }, ], }, - ].concat( - useQueryHookNames.map((queryHook) => ({ - name: `result of ${queryHook} is passed to ${reactHookInvocation} as dependency`, - code: ` + ] + .concat( + useQueryHookNames.map((queryHook) => ({ + name: `result of ${queryHook} is passed to ${reactHookInvocation} as dependency`, + code: ` ${reactHookImport} import { ${queryHook} } from "@tanstack/react-query"; @@ -100,17 +103,18 @@ const baseTestCases = { return; } `, - errors: [ - { - messageId: 'noUnstableDeps', - data: { reactHook: reactHookAlias, queryHook }, - }, - ], - })), - ).concat([ - { - name: `result of useQueries without combine is passed to ${reactHookInvocation} as dependency`, - code: ` + errors: [ + { + messageId: 'noUnstableDeps', + data: { reactHook: reactHookAlias, queryHook }, + }, + ], + })), + ) + .concat([ + { + name: `result of useQueries without combine is passed to ${reactHookInvocation} as dependency`, + code: ` ${reactHookImport} import { useQueries } from "@tanstack/react-query"; @@ -124,14 +128,14 @@ const baseTestCases = { return; } `, - errors: [ - { - messageId: 'noUnstableDeps', - data: { reactHook: reactHookAlias, queryHook: 'useQueries' }, - }, - ], - }, - ]), + errors: [ + { + messageId: 'noUnstableDeps', + data: { reactHook: reactHookAlias, queryHook: 'useQueries' }, + }, + ], + }, + ]), } const testCases = (reactHookName: string) => [ diff --git a/packages/eslint-plugin-query/src/rules/no-unstable-deps/no-unstable-deps.rule.ts b/packages/eslint-plugin-query/src/rules/no-unstable-deps/no-unstable-deps.rule.ts index eb3e2f9422..7d643f4ec1 100644 --- a/packages/eslint-plugin-query/src/rules/no-unstable-deps/no-unstable-deps.rule.ts +++ b/packages/eslint-plugin-query/src/rules/no-unstable-deps/no-unstable-deps.rule.ts @@ -67,16 +67,20 @@ export const rule = createRule({ } } - function hasCombineProperty(callExpression: TSESTree.CallExpression): boolean { + function hasCombineProperty( + callExpression: TSESTree.CallExpression, + ): boolean { if (callExpression.arguments.length === 0) return false const firstArg = callExpression.arguments[0] - if (!firstArg || firstArg.type !== AST_NODE_TYPES.ObjectExpression) return false + if (!firstArg || firstArg.type !== AST_NODE_TYPES.ObjectExpression) + return false - return firstArg.properties.some(prop => - prop.type === AST_NODE_TYPES.Property && - prop.key.type === AST_NODE_TYPES.Identifier && - prop.key.name === 'combine' + return firstArg.properties.some( + (prop) => + prop.type === AST_NODE_TYPES.Property && + prop.key.type === AST_NODE_TYPES.Identifier && + prop.key.name === 'combine', ) } @@ -108,7 +112,10 @@ export const rule = createRule({ allHookNames.includes(node.init.callee.name) ) { // Special case for useQueries with combine property - it's stable - if (node.init.callee.name === 'useQueries' && hasCombineProperty(node.init)) { + if ( + node.init.callee.name === 'useQueries' && + hasCombineProperty(node.init) + ) { // Don't track useQueries with combine as unstable return }