Skip to content

Commit

Permalink
generalize to all affected query hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
schiller-manuel committed Aug 19, 2024
1 parent 3eac412 commit dd1ce96
Show file tree
Hide file tree
Showing 9 changed files with 225 additions and 132 deletions.
4 changes: 2 additions & 2 deletions docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -794,8 +794,8 @@
"to": "eslint/no-rest-destructuring"
},
{
"label": "No Mutation in Deps",
"to": "eslint/no-mutation-in-deps"
"label": "No Unstable Query/Mutation in Deps",
"to": "eslint/no-unstable-query-mutation-in-deps"
}
]
},
Expand Down
2 changes: 1 addition & 1 deletion docs/eslint/eslint-plugin-query.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,4 @@ Alternatively, add `@tanstack/eslint-plugin-query` to the plugins section, and c
- [@tanstack/query/exhaustive-deps](../exhaustive-deps)
- [@tanstack/query/no-rest-destructuring](../no-rest-destructuring)
- [@tanstack/query/stable-query-client](../stable-query-client)
- [@tanstack/query/no-mutation-in-deps](../no-mutation-in-deps.md)
- [@tanstack/query/no-unstable-query-mutation-in-deps](../no-unstable-query-mutation-in-deps.md)
46 changes: 0 additions & 46 deletions docs/eslint/no-mutation-in-deps.md

This file was deleted.

56 changes: 56 additions & 0 deletions docs/eslint/no-unstable-query-mutation-in-deps.md
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

This file was deleted.

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,
}),
})
},
)
})
4 changes: 2 additions & 2 deletions packages/eslint-plugin-query/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Object.assign(plugin.configs, {
'@tanstack/query/exhaustive-deps': 'error',
'@tanstack/query/no-rest-destructuring': 'warn',
'@tanstack/query/stable-query-client': 'error',
'@tanstack/query/no-mutation-in-deps': 'error',
'@tanstack/query/no-unstable-query-mutation-in-deps': 'error',
},
},
'flat/recommended': [
Expand All @@ -40,7 +40,7 @@ Object.assign(plugin.configs, {
'@tanstack/query/exhaustive-deps': 'error',
'@tanstack/query/no-rest-destructuring': 'warn',
'@tanstack/query/stable-query-client': 'error',
'@tanstack/query/no-mutation-in-deps': 'error',
'@tanstack/query/no-unstable-query-mutation-in-deps': 'error',
},
},
],
Expand Down
4 changes: 2 additions & 2 deletions packages/eslint-plugin-query/src/rules.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as exhaustiveDeps from './rules/exhaustive-deps/exhaustive-deps.rule'
import * as stableQueryClient from './rules/stable-query-client/stable-query-client.rule'
import * as noRestDestructuring from './rules/no-rest-destructuring/no-rest-destructuring.rule'
import * as noMutationInDeps from './rules/no-mutation-in-deps/no-mutation-in-deps.rule'
import * as noUnstableQueryMutationInDeps from './rules/no-unstable-query-mutation-in-deps/no-unstable-query-mutation-in-deps.rule'
import type { ESLintUtils } from '@typescript-eslint/utils'
import type { ExtraRuleDocs } from './types'

Expand All @@ -17,5 +17,5 @@ export const rules: Record<
[exhaustiveDeps.name]: exhaustiveDeps.rule,
[stableQueryClient.name]: stableQueryClient.rule,
[noRestDestructuring.name]: noRestDestructuring.rule,
[noMutationInDeps.name]: noMutationInDeps.rule,
[noUnstableQueryMutationInDeps.name]: noUnstableQueryMutationInDeps.rule,
}
Loading

0 comments on commit dd1ce96

Please sign in to comment.