-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
generalize to all affected query hooks
- Loading branch information
1 parent
3eac412
commit dd1ce96
Showing
9 changed files
with
225 additions
and
132 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
--- | ||
id: no-unstable-query-mutation-in-deps | ||
title: Disallow putting the result of query hooks directly in a React hook dependency array | ||
--- | ||
|
||
The object returned from the following query hooks is **not** referentially stable: | ||
|
||
- `useQuery` | ||
- `useSuspenseQuery` | ||
- `useQueries` | ||
- `useSuspenseQueries` | ||
- `useInfiniteQuery` | ||
- `useSuspenseInfiniteQuery` | ||
- `useMutation` | ||
|
||
The object returned from those hooks should **not** be put directly into the dependency array of a React hook (e.g. `useEffect`, `useMemo`, `useCallback`). | ||
Instead, destructure the return value of the query hook and pass the destructured values into the dependency array of the React hook. | ||
|
||
## Rule Details | ||
|
||
Examples of **incorrect** code for this rule: | ||
|
||
```tsx | ||
/* eslint "@tanstack/query/no-unstable-query-mutation-in-deps": "warn" */ | ||
import { useCallback } from 'React' | ||
import { useMutation } from '@tanstack/react-query' | ||
|
||
function Component() { | ||
const mutation = useMutation({ mutationFn: (value: string) => value }) | ||
const callback = useCallback(() => { | ||
mutation.mutate('hello') | ||
}, [mutation]) | ||
return null | ||
} | ||
``` | ||
|
||
Examples of **correct** code for this rule: | ||
|
||
```tsx | ||
/* eslint "@tanstack/query/no-unstable-query-mutation-in-deps": "warn" */ | ||
import { useCallback } from 'React' | ||
import { useMutation } from '@tanstack/react-query' | ||
|
||
function Component() { | ||
const { mutate } = useMutation({ mutationFn: (value: string) => value }) | ||
const callback = useCallback(() => { | ||
mutate('hello') | ||
}, [mutate]) | ||
return null | ||
} | ||
``` | ||
|
||
## Attributes | ||
|
||
- [x] ✅ Recommended | ||
- [ ] 🔧 Fixable |
64 changes: 0 additions & 64 deletions
64
packages/eslint-plugin-query/src/__tests__/no-mutation-in-deps.test.ts
This file was deleted.
Oops, something went wrong.
128 changes: 128 additions & 0 deletions
128
packages/eslint-plugin-query/src/__tests__/no-unstable-query-mutation-in-deps.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import { RuleTester } from '@typescript-eslint/rule-tester' | ||
import { | ||
reactHookNames, | ||
rule, | ||
useQueryHookNames, | ||
} from '../rules/no-unstable-query-mutation-in-deps/no-unstable-query-mutation-in-deps.rule' | ||
|
||
const ruleTester = new RuleTester({ | ||
parser: '@typescript-eslint/parser', | ||
settings: {}, | ||
}) | ||
|
||
interface TestCase { | ||
reactHookImport: string | ||
reactHookInvocation: string | ||
reactHookAlias: string | ||
} | ||
const baseTestCases = { | ||
valid: ({ reactHookImport, reactHookInvocation, reactHookAlias }: TestCase) => | ||
[ | ||
{ | ||
name: `should pass when destructured mutate is passed to ${reactHookAlias} as dependency`, | ||
code: ` | ||
${reactHookImport} | ||
import { useMutation } from "@tanstack/react-query"; | ||
function Component() { | ||
const { mutate } = useMutation({ mutationFn: (value: string) => value }); | ||
const callback = ${reactHookInvocation}(() => { mutate('hello') }, [mutate]); | ||
return; | ||
} | ||
`, | ||
}, | ||
].concat( | ||
useQueryHookNames.map((queryHook) => ({ | ||
name: `should pass result of ${queryHook} is passed to ${reactHookInvocation} as dependency`, | ||
code: ` | ||
${reactHookImport} | ||
import { ${queryHook} } from "@tanstack/react-query"; | ||
function Component() { | ||
const { refetch } = ${queryHook}({ queryFn: (value: string) => value }); | ||
const callback = ${reactHookInvocation}(() => { query.refetch() }, [refetch]); | ||
return; | ||
} | ||
`, | ||
})), | ||
), | ||
invalid: ({ reactHookImport, reactHookInvocation, reactHookAlias }: TestCase) => | ||
[ | ||
{ | ||
name: `result of useMutation is passed to ${reactHookInvocation} as dependency `, | ||
code: ` | ||
${reactHookImport} | ||
import { useMutation } from "@tanstack/react-query"; | ||
function Component() { | ||
const mutation = useMutation({ mutationFn: (value: string) => value }); | ||
const callback = ${reactHookInvocation}(() => { mutation.mutate('hello') }, [mutation]); | ||
return; | ||
} | ||
`, | ||
errors: [ | ||
{ | ||
messageId: 'noUnstableQueryMutationInDeps', | ||
data: { reactHook: reactHookAlias, queryHook: 'useMutation' }, | ||
}, | ||
], | ||
}, | ||
].concat( | ||
useQueryHookNames.map((queryHook) => ({ | ||
name: `result of ${queryHook} is passed to ${reactHookInvocation} as dependency`, | ||
code: ` | ||
${reactHookImport} | ||
import { ${queryHook} } from "@tanstack/react-query"; | ||
function Component() { | ||
const query = ${queryHook}({ queryFn: (value: string) => value }); | ||
const callback = ${reactHookInvocation}(() => { query.refetch() }, [query]); | ||
return; | ||
} | ||
`, | ||
errors: [ | ||
{ | ||
messageId: 'noUnstableQueryMutationInDeps', | ||
data: { reactHook: reactHookAlias, queryHook }, | ||
}, | ||
], | ||
})), | ||
), | ||
} | ||
|
||
const testCases = (reactHookName: string) => [ | ||
{ | ||
reactHookImport: 'import * as React from "React";', | ||
reactHookInvocation: `React.${reactHookName}`, | ||
reactHookAlias: reactHookName, | ||
}, | ||
{ | ||
reactHookImport: `import { ${reactHookName} } from "React";`, | ||
reactHookInvocation: reactHookName, | ||
reactHookAlias: reactHookName, | ||
}, | ||
{ | ||
reactHookImport: `import { ${reactHookName} as useAlias } from "React";`, | ||
reactHookInvocation: 'useAlias', | ||
reactHookAlias: 'useAlias', | ||
}, | ||
] | ||
|
||
reactHookNames.forEach((reactHookName) => { | ||
testCases(reactHookName).forEach( | ||
({ reactHookInvocation, reactHookAlias, reactHookImport }) => { | ||
ruleTester.run('no-unstable-query-mutation-in-deps', rule, { | ||
valid: baseTestCases.valid({ | ||
reactHookImport, | ||
reactHookInvocation, | ||
reactHookAlias, | ||
}), | ||
invalid: baseTestCases.invalid({ | ||
reactHookImport, | ||
reactHookInvocation, | ||
reactHookAlias, | ||
}), | ||
}) | ||
}, | ||
) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.