diff --git a/.changeset/ten-crabs-glow.md b/.changeset/ten-crabs-glow.md new file mode 100644 index 0000000000..23b228b4a9 --- /dev/null +++ b/.changeset/ten-crabs-glow.md @@ -0,0 +1,5 @@ +--- +"@tanstack/eslint-plugin-query": patch +--- + +fix: allow useQueries with combine property in no-unstable-deps rule 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..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,8 +42,28 @@ const baseTestCases = { return; } `, - })), - ), + })), + ) + .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, @@ -68,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"; @@ -81,14 +103,39 @@ const baseTestCases = { return; } `, - errors: [ - { - messageId: 'noUnstableDeps', - data: { reactHook: reactHookAlias, queryHook }, - }, - ], - })), - ), + 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"; + + 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..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,6 +67,23 @@ 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 +111,14 @@ 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) } },