Skip to content

Commit dd1ce96

Browse files
generalize to all affected query hooks
1 parent 3eac412 commit dd1ce96

File tree

9 files changed

+225
-132
lines changed

9 files changed

+225
-132
lines changed

docs/config.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -794,8 +794,8 @@
794794
"to": "eslint/no-rest-destructuring"
795795
},
796796
{
797-
"label": "No Mutation in Deps",
798-
"to": "eslint/no-mutation-in-deps"
797+
"label": "No Unstable Query/Mutation in Deps",
798+
"to": "eslint/no-unstable-query-mutation-in-deps"
799799
}
800800
]
801801
},

docs/eslint/eslint-plugin-query.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,4 @@ Alternatively, add `@tanstack/eslint-plugin-query` to the plugins section, and c
8484
- [@tanstack/query/exhaustive-deps](../exhaustive-deps)
8585
- [@tanstack/query/no-rest-destructuring](../no-rest-destructuring)
8686
- [@tanstack/query/stable-query-client](../stable-query-client)
87-
- [@tanstack/query/no-mutation-in-deps](../no-mutation-in-deps.md)
87+
- [@tanstack/query/no-unstable-query-mutation-in-deps](../no-unstable-query-mutation-in-deps.md)

docs/eslint/no-mutation-in-deps.md

-46
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
---
2+
id: no-unstable-query-mutation-in-deps
3+
title: Disallow putting the result of query hooks directly in a React hook dependency array
4+
---
5+
6+
The object returned from the following query hooks is **not** referentially stable:
7+
8+
- `useQuery`
9+
- `useSuspenseQuery`
10+
- `useQueries`
11+
- `useSuspenseQueries`
12+
- `useInfiniteQuery`
13+
- `useSuspenseInfiniteQuery`
14+
- `useMutation`
15+
16+
The object returned from those hooks should **not** be put directly into the dependency array of a React hook (e.g. `useEffect`, `useMemo`, `useCallback`).
17+
Instead, destructure the return value of the query hook and pass the destructured values into the dependency array of the React hook.
18+
19+
## Rule Details
20+
21+
Examples of **incorrect** code for this rule:
22+
23+
```tsx
24+
/* eslint "@tanstack/query/no-unstable-query-mutation-in-deps": "warn" */
25+
import { useCallback } from 'React'
26+
import { useMutation } from '@tanstack/react-query'
27+
28+
function Component() {
29+
const mutation = useMutation({ mutationFn: (value: string) => value })
30+
const callback = useCallback(() => {
31+
mutation.mutate('hello')
32+
}, [mutation])
33+
return null
34+
}
35+
```
36+
37+
Examples of **correct** code for this rule:
38+
39+
```tsx
40+
/* eslint "@tanstack/query/no-unstable-query-mutation-in-deps": "warn" */
41+
import { useCallback } from 'React'
42+
import { useMutation } from '@tanstack/react-query'
43+
44+
function Component() {
45+
const { mutate } = useMutation({ mutationFn: (value: string) => value })
46+
const callback = useCallback(() => {
47+
mutate('hello')
48+
}, [mutate])
49+
return null
50+
}
51+
```
52+
53+
## Attributes
54+
55+
- [x] ✅ Recommended
56+
- [ ] 🔧 Fixable

packages/eslint-plugin-query/src/__tests__/no-mutation-in-deps.test.ts

-64
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import { RuleTester } from '@typescript-eslint/rule-tester'
2+
import {
3+
reactHookNames,
4+
rule,
5+
useQueryHookNames,
6+
} from '../rules/no-unstable-query-mutation-in-deps/no-unstable-query-mutation-in-deps.rule'
7+
8+
const ruleTester = new RuleTester({
9+
parser: '@typescript-eslint/parser',
10+
settings: {},
11+
})
12+
13+
interface TestCase {
14+
reactHookImport: string
15+
reactHookInvocation: string
16+
reactHookAlias: string
17+
}
18+
const baseTestCases = {
19+
valid: ({ reactHookImport, reactHookInvocation, reactHookAlias }: TestCase) =>
20+
[
21+
{
22+
name: `should pass when destructured mutate is passed to ${reactHookAlias} as dependency`,
23+
code: `
24+
${reactHookImport}
25+
import { useMutation } from "@tanstack/react-query";
26+
27+
function Component() {
28+
const { mutate } = useMutation({ mutationFn: (value: string) => value });
29+
const callback = ${reactHookInvocation}(() => { mutate('hello') }, [mutate]);
30+
return;
31+
}
32+
`,
33+
},
34+
].concat(
35+
useQueryHookNames.map((queryHook) => ({
36+
name: `should pass result of ${queryHook} is passed to ${reactHookInvocation} as dependency`,
37+
code: `
38+
${reactHookImport}
39+
import { ${queryHook} } from "@tanstack/react-query";
40+
41+
function Component() {
42+
const { refetch } = ${queryHook}({ queryFn: (value: string) => value });
43+
const callback = ${reactHookInvocation}(() => { query.refetch() }, [refetch]);
44+
return;
45+
}
46+
`,
47+
})),
48+
),
49+
invalid: ({ reactHookImport, reactHookInvocation, reactHookAlias }: TestCase) =>
50+
[
51+
{
52+
name: `result of useMutation is passed to ${reactHookInvocation} as dependency `,
53+
code: `
54+
${reactHookImport}
55+
import { useMutation } from "@tanstack/react-query";
56+
57+
function Component() {
58+
const mutation = useMutation({ mutationFn: (value: string) => value });
59+
const callback = ${reactHookInvocation}(() => { mutation.mutate('hello') }, [mutation]);
60+
return;
61+
}
62+
`,
63+
errors: [
64+
{
65+
messageId: 'noUnstableQueryMutationInDeps',
66+
data: { reactHook: reactHookAlias, queryHook: 'useMutation' },
67+
},
68+
],
69+
},
70+
].concat(
71+
useQueryHookNames.map((queryHook) => ({
72+
name: `result of ${queryHook} is passed to ${reactHookInvocation} as dependency`,
73+
code: `
74+
${reactHookImport}
75+
import { ${queryHook} } from "@tanstack/react-query";
76+
77+
function Component() {
78+
const query = ${queryHook}({ queryFn: (value: string) => value });
79+
const callback = ${reactHookInvocation}(() => { query.refetch() }, [query]);
80+
return;
81+
}
82+
`,
83+
errors: [
84+
{
85+
messageId: 'noUnstableQueryMutationInDeps',
86+
data: { reactHook: reactHookAlias, queryHook },
87+
},
88+
],
89+
})),
90+
),
91+
}
92+
93+
const testCases = (reactHookName: string) => [
94+
{
95+
reactHookImport: 'import * as React from "React";',
96+
reactHookInvocation: `React.${reactHookName}`,
97+
reactHookAlias: reactHookName,
98+
},
99+
{
100+
reactHookImport: `import { ${reactHookName} } from "React";`,
101+
reactHookInvocation: reactHookName,
102+
reactHookAlias: reactHookName,
103+
},
104+
{
105+
reactHookImport: `import { ${reactHookName} as useAlias } from "React";`,
106+
reactHookInvocation: 'useAlias',
107+
reactHookAlias: 'useAlias',
108+
},
109+
]
110+
111+
reactHookNames.forEach((reactHookName) => {
112+
testCases(reactHookName).forEach(
113+
({ reactHookInvocation, reactHookAlias, reactHookImport }) => {
114+
ruleTester.run('no-unstable-query-mutation-in-deps', rule, {
115+
valid: baseTestCases.valid({
116+
reactHookImport,
117+
reactHookInvocation,
118+
reactHookAlias,
119+
}),
120+
invalid: baseTestCases.invalid({
121+
reactHookImport,
122+
reactHookInvocation,
123+
reactHookAlias,
124+
}),
125+
})
126+
},
127+
)
128+
})

packages/eslint-plugin-query/src/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Object.assign(plugin.configs, {
2828
'@tanstack/query/exhaustive-deps': 'error',
2929
'@tanstack/query/no-rest-destructuring': 'warn',
3030
'@tanstack/query/stable-query-client': 'error',
31-
'@tanstack/query/no-mutation-in-deps': 'error',
31+
'@tanstack/query/no-unstable-query-mutation-in-deps': 'error',
3232
},
3333
},
3434
'flat/recommended': [
@@ -40,7 +40,7 @@ Object.assign(plugin.configs, {
4040
'@tanstack/query/exhaustive-deps': 'error',
4141
'@tanstack/query/no-rest-destructuring': 'warn',
4242
'@tanstack/query/stable-query-client': 'error',
43-
'@tanstack/query/no-mutation-in-deps': 'error',
43+
'@tanstack/query/no-unstable-query-mutation-in-deps': 'error',
4444
},
4545
},
4646
],

packages/eslint-plugin-query/src/rules.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as exhaustiveDeps from './rules/exhaustive-deps/exhaustive-deps.rule'
22
import * as stableQueryClient from './rules/stable-query-client/stable-query-client.rule'
33
import * as noRestDestructuring from './rules/no-rest-destructuring/no-rest-destructuring.rule'
4-
import * as noMutationInDeps from './rules/no-mutation-in-deps/no-mutation-in-deps.rule'
4+
import * as noUnstableQueryMutationInDeps from './rules/no-unstable-query-mutation-in-deps/no-unstable-query-mutation-in-deps.rule'
55
import type { ESLintUtils } from '@typescript-eslint/utils'
66
import type { ExtraRuleDocs } from './types'
77

@@ -17,5 +17,5 @@ export const rules: Record<
1717
[exhaustiveDeps.name]: exhaustiveDeps.rule,
1818
[stableQueryClient.name]: stableQueryClient.rule,
1919
[noRestDestructuring.name]: noRestDestructuring.rule,
20-
[noMutationInDeps.name]: noMutationInDeps.rule,
20+
[noUnstableQueryMutationInDeps.name]: noUnstableQueryMutationInDeps.rule,
2121
}

0 commit comments

Comments
 (0)