diff --git a/.changeset/fix-no-unstable-deps-array-pattern.md b/.changeset/fix-no-unstable-deps-array-pattern.md new file mode 100644 index 00000000000..a0da3857512 --- /dev/null +++ b/.changeset/fix-no-unstable-deps-array-pattern.md @@ -0,0 +1,5 @@ +--- +"@tanstack/eslint-plugin-query": patch +--- + +fix(no-unstable-deps): handle array-destructured useQueries and useSuspenseQueries results 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 be3419242e5..43bbe0f494f 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 @@ -82,6 +82,25 @@ const baseTestCases = { `, }, ]) + .concat([ + { + name: `should pass when useQueries is array-destructured and element properties are used with ${reactHookAlias}`, + code: ` + ${reactHookImport} + import { useQueries } from "@tanstack/react-query"; + + function Component() { + const [{ data }] = useQueries({ + queries: [ + { queryKey: ['test'], queryFn: () => 'test' } + ] + }); + const callback = ${reactHookInvocation}(() => { data }, [data]); + return; + } + `, + }, + ]) .concat([ { name: `should pass when useQuery is imported from non-TanStack source and used with ${reactHookAlias}`, @@ -181,6 +200,82 @@ const baseTestCases = { }, ], }, + ]) + .concat([ + { + name: `array-destructured useQueries element is passed to ${reactHookInvocation} as dependency`, + code: ` + ${reactHookImport} + import { useQueries } from "@tanstack/react-query"; + + function Component() { + const [userQuery, postsQuery] = useQueries({ + queries: [ + { queryKey: ['user'], queryFn: () => 'user' }, + { queryKey: ['posts'], queryFn: () => 'posts' } + ] + }); + const callback = ${reactHookInvocation}(() => { userQuery.data }, [userQuery]); + return; + } + `, + errors: [ + { + messageId: 'noUnstableDeps', + data: { reactHook: reactHookAlias, queryHook: 'useQueries' }, + }, + ], + }, + { + name: `array-destructured useSuspenseQueries element is passed to ${reactHookInvocation} as dependency`, + code: ` + ${reactHookImport} + import { useSuspenseQueries } from "@tanstack/react-query"; + + function Component() { + const [query] = useSuspenseQueries({ + queries: [ + { queryKey: ['test'], queryFn: () => 'test' } + ] + }); + const callback = ${reactHookInvocation}(() => { query.data }, [query]); + return; + } + `, + errors: [ + { + messageId: 'noUnstableDeps', + data: { + reactHook: reactHookAlias, + queryHook: 'useSuspenseQueries', + }, + }, + ], + }, + { + name: `rest element of array-destructured useQueries is passed to ${reactHookInvocation} as dependency`, + code: ` + ${reactHookImport} + import { useQueries } from "@tanstack/react-query"; + + function Component() { + const [firstQuery, ...restQueries] = useQueries({ + queries: [ + { queryKey: ['a'], queryFn: () => 'a' }, + { queryKey: ['b'], queryFn: () => 'b' } + ] + }); + const callback = ${reactHookInvocation}(() => {}, [restQueries]); + return; + } + `, + errors: [ + { + messageId: 'noUnstableDeps', + data: { reactHook: reactHookAlias, queryHook: 'useQueries' }, + }, + ], + }, ]), } 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 25d093f6907..621993a2c16 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 @@ -64,6 +64,20 @@ export const rule = createRule({ ) { if (pattern.type === AST_NODE_TYPES.Identifier) { trackedVariables[pattern.name] = queryHook + } else if (pattern.type === AST_NODE_TYPES.ArrayPattern) { + for (const element of pattern.elements) { + if (element === null) { + continue + } + if (element.type === AST_NODE_TYPES.Identifier) { + trackedVariables[element.name] = queryHook + } else if ( + element.type === AST_NODE_TYPES.RestElement && + element.argument.type === AST_NODE_TYPES.Identifier + ) { + trackedVariables[element.argument.name] = queryHook + } + } } }