Skip to content

Commit 0028a14

Browse files
feat: remove overloads codemod (#5046)
* feat(codemod): initial implementation of remove overloads codemod * feat(codemod): add nearly working implementation of `transformQueryFnAwareUsages` function * chore(codemod): move "transformer" functions under the `transformers` directory * chore(codemod): add better type definitions for `jscodeshift` parameters * feat(codemod): finish the implementation of `transformQueryFnAwareUsages` function * refactor(codemod): introduce `isSpreadElement` and `isFunctionDefinition` utility functions * refactor(codemod): re-use previously written utility functions in transformer functions * chore(codemod): add codemod usage instructions --------- Co-authored-by: Dominik Dorfmeister <[email protected]>
1 parent 6c3b213 commit 0028a14

File tree

11 files changed

+1315
-1
lines changed

11 files changed

+1315
-1
lines changed

docs/react/guides/migrating-to-v5.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,41 @@ now we only support the object format.
9797
+ queryClient.getQueryState(queryKey)
9898
```
9999

100+
#### Codemod
101+
102+
To make the remove overloads migration easier, v5 comes with a codemod.
103+
104+
> The codemod is a best efforts attempt to help you migrate the breaking change. Please review the generated code thoroughly! Also, there are edge cases that cannot be found by the code mod, so please keep an eye on the log output.
105+
106+
If you want to run it against `.js` or `.jsx` files, please use the command below:
107+
108+
```
109+
npx jscodeshift ./path/to/src/ \
110+
--extensions=js,jsx \
111+
--transform=./node_modules/@tanstack/react-query/codemods/v5/remove-overloads/remove-overloads.js
112+
```
113+
114+
If you want to run it against `.ts` or `.tsx` files, please use the command below:
115+
116+
```
117+
npx jscodeshift ./path/to/src/ \
118+
--extensions=ts,tsx \
119+
--parser=tsx \
120+
--transform=./node_modules/@tanstack/react-query/codemods/v5/remove-overloads/remove-overloads.js
121+
```
122+
123+
Please note in the case of `TypeScript` you need to use `tsx` as the parser; otherwise, the codemod won't be applied properly!
124+
125+
**Note:** Applying the codemod might break your code formatting, so please don't forget to run `prettier` and/or `eslint` after you've applied the codemod!
126+
127+
A few notes about how codemod works:
128+
129+
- Generally, we're looking for the lucky case, when the first parameter is an object expression and contains the "queryKey" or "mutationKey" property (depending on which hook/method call is being transformed). If this is the case, your code already matches the new signature, so the codemod won't touch it. 🎉
130+
- If the condition above is not fulfilled, then the codemod will check whether the first parameter is an array expression or an identifier that references an array expression. If this is the case, the codemod will put it into an object expression, then it will be the first parameter.
131+
- If object parameters can be inferred, the codemod will attempt to copy the already existing properties to the newly created one.
132+
- If the codemod cannot infer the usage, then it will leave a message on the console. The message contains the file name and the line number of the usage. In this case, you need to do the migration manually.
133+
- If the transformation results in an error, you will also see a message on the console. This message will notify you something unexpected happened, please do the migration manually.
134+
100135
### The `remove` method has been removed from useQuery
101136

102137
Previously, remove method used to remove the query from the queryCache without informing observers about it. It was best used to remove data imperatively that is no longer needed, e.g. when logging a user out.

packages/codemods/src/utils/index.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,39 @@ module.exports = ({ root, jscodeshift }) => {
117117
const isNewExpression = (node) =>
118118
jscodeshift.match(node, { type: jscodeshift.NewExpression.name })
119119

120+
const isArrayExpression = (node) =>
121+
jscodeshift.match(node, { type: jscodeshift.ArrayExpression.name })
122+
123+
const isObjectExpression = (node) =>
124+
jscodeshift.match(node, { type: jscodeshift.ObjectExpression.name })
125+
126+
const isObjectProperty = (node) =>
127+
jscodeshift.match(node, { type: jscodeshift.ObjectProperty.name })
128+
129+
const isSpreadElement = (node) =>
130+
jscodeshift.match(node, { type: jscodeshift.SpreadElement.name })
131+
132+
/**
133+
* @param {import('jscodeshift').Node} node
134+
* @returns {boolean}
135+
*/
136+
const isFunctionDefinition = (node) => {
137+
const isArrowFunctionExpression = jscodeshift.match(node, {
138+
type: jscodeshift.ArrowFunctionExpression.name,
139+
})
140+
const isFunctionExpression = jscodeshift.match(node, {
141+
type: jscodeshift.FunctionExpression.name,
142+
})
143+
144+
return isArrowFunctionExpression || isFunctionExpression
145+
}
146+
147+
const warn = (message) => {
148+
if (process.env.NODE_ENV !== 'test') {
149+
console.warn(message)
150+
}
151+
}
152+
120153
const isClassInstantiationOf = (node, selector) => {
121154
if (!isNewExpression(node)) {
122155
return false
@@ -158,7 +191,13 @@ module.exports = ({ root, jscodeshift }) => {
158191
isFunctionCallOf,
159192
isIdentifier,
160193
isMemberExpression,
194+
isArrayExpression,
195+
isObjectExpression,
196+
isObjectProperty,
197+
isSpreadElement,
198+
isFunctionDefinition,
161199
locateImports,
200+
warn,
162201
queryClient: {
163202
findQueryClientIdentifiers,
164203
},

packages/codemods/src/v5/README.md

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
import * as React from 'react'
2+
import {
3+
useIsFetching,
4+
useIsMutating,
5+
useQueryClient,
6+
} from '@tanstack/react-query'
7+
import { queryKeysFromAnotherModule } from '../another/module'
8+
9+
export const WithKnownParameters = () => {
10+
useIsFetching(['foo', 'bar'])
11+
useIsFetching(['foo', 'bar'], { queryKey: ['todos'], type: 'all', exact: true })
12+
useIsFetching(['foo', 'bar'], { queryKey: ['todos'], type: 'all', exact: true }, { context: undefined })
13+
useIsFetching(['foo', 'bar'], { type: 'all', exact: true })
14+
useIsFetching(['foo', 'bar'], { type: 'all', exact: true }, { context: undefined })
15+
useIsFetching({ queryKey: ['foo', 'bar'], type: 'all', exact: true })
16+
useIsFetching({ queryKey: ['foo', 'bar'], type: 'all', exact: true }, { context: undefined })
17+
18+
useIsMutating(['foo', 'bar'])
19+
useIsMutating(['foo', 'bar'], { exact: true })
20+
useIsMutating(['foo', 'bar'], { exact: true }, { context: undefined })
21+
useIsMutating({ mutationKey: ['foo', 'bar'], exact: true })
22+
useIsMutating({ mutationKey: ['foo', 'bar'], exact: true }, { context: undefined })
23+
24+
// QueryClient methods
25+
// --- Instantiated hook call.
26+
const queryClient = useQueryClient()
27+
queryClient.cancelQueries(['foo', 'bar'])
28+
queryClient.cancelQueries(['foo', 'bar'], { queryKey: ['todos'], type: 'all', exact: true })
29+
queryClient.cancelQueries(['foo', 'bar'], { queryKey: ['todos'], type: 'all', exact: true }, { silent: true })
30+
queryClient.cancelQueries(['foo', 'bar'], { type: 'all', exact: true })
31+
queryClient.cancelQueries(['foo', 'bar'], { type: 'all', exact: true }, { silent: true })
32+
queryClient.cancelQueries({ queryKey: ['foo', 'bar'], type: 'all', exact: true })
33+
queryClient.cancelQueries({ queryKey: ['foo', 'bar'], type: 'all', exact: true }, { silent: true })
34+
35+
queryClient.getQueriesData(['foo', 'bar'])
36+
queryClient.getQueriesData({ queryKey: ['foo', 'bar'], type: 'all', exact: true })
37+
38+
queryClient.invalidateQueries(['foo', 'bar'])
39+
queryClient.invalidateQueries(['foo', 'bar'], { queryKey: ['todos'], type: 'all', exact: true })
40+
queryClient.invalidateQueries(['foo', 'bar'], { queryKey: ['todos'], type: 'all', exact: true }, { cancelRefetch: false, throwOnError: true })
41+
queryClient.invalidateQueries(['foo', 'bar'], { type: 'all', exact: true })
42+
queryClient.invalidateQueries(['foo', 'bar'], { type: 'all', exact: true }, { cancelRefetch: false, throwOnError: true })
43+
queryClient.invalidateQueries({ queryKey: ['foo', 'bar'], type: 'all', exact: true })
44+
queryClient.invalidateQueries({ queryKey: ['foo', 'bar'], type: 'all', exact: true }, { cancelRefetch: false, throwOnError: true })
45+
46+
queryClient.isFetching(['foo', 'bar'])
47+
queryClient.isFetching(['foo', 'bar'], { queryKey: ['todos'], type: 'all', exact: true })
48+
queryClient.isFetching(['foo', 'bar'], { type: 'all', exact: true })
49+
queryClient.isFetching({ queryKey: ['foo', 'bar'], type: 'all', exact: true })
50+
51+
queryClient.refetchQueries(['foo', 'bar'])
52+
queryClient.refetchQueries(['foo', 'bar'], { queryKey: ['todos'], type: 'all', exact: true })
53+
queryClient.refetchQueries(['foo', 'bar'], { queryKey: ['todos'], type: 'all', exact: true }, { cancelRefetch: false, throwOnError: true })
54+
queryClient.refetchQueries(['foo', 'bar'], { type: 'all', exact: true })
55+
queryClient.refetchQueries(['foo', 'bar'], { type: 'all', exact: true }, { cancelRefetch: false, throwOnError: true })
56+
queryClient.refetchQueries({ queryKey: ['foo', 'bar'], type: 'all', exact: true })
57+
queryClient.refetchQueries({ queryKey: ['foo', 'bar'], type: 'all', exact: true }, { cancelRefetch: false, throwOnError: true })
58+
59+
queryClient.removeQueries(['foo', 'bar'])
60+
queryClient.removeQueries(['foo', 'bar'], { queryKey: ['todos'], type: 'all', exact: true })
61+
queryClient.removeQueries({ queryKey: ['foo', 'bar'], type: 'all', exact: true })
62+
63+
queryClient.resetQueries(['foo', 'bar'])
64+
queryClient.resetQueries(['foo', 'bar'], { queryKey: ['todos'], type: 'all', exact: true })
65+
queryClient.resetQueries(['foo', 'bar'], { queryKey: ['todos'], type: 'all', exact: true }, { cancelRefetch: false, throwOnError: true })
66+
queryClient.resetQueries(['foo', 'bar'], { type: 'all', exact: true })
67+
queryClient.resetQueries(['foo', 'bar'], { type: 'all', exact: true }, { cancelRefetch: false, throwOnError: true })
68+
queryClient.resetQueries({ queryKey: ['foo', 'bar'], exact: true })
69+
queryClient.resetQueries({ queryKey: ['foo', 'bar'], exact: true }, { cancelRefetch: false, throwOnError: true })
70+
71+
queryClient.setQueriesData(['foo', 'bar'], null)
72+
queryClient.setQueriesData(['foo', 'bar'], null, { updatedAt: 1000 })
73+
queryClient.setQueriesData({ queryKey: ['foo', 'bar'] }, null)
74+
queryClient.setQueriesData({ queryKey: ['foo', 'bar'] }, null, { updatedAt: 1000 })
75+
76+
queryClient.fetchQuery(['foo', 'bar'])
77+
queryClient.fetchQuery(['foo', 'bar'], { queryKey: ['todos'], staleTime: 1000 })
78+
queryClient.fetchQuery(['foo', 'bar'], { queryKey: ['todos'], queryFn: () => 'data', staleTime: 1000 })
79+
queryClient.fetchQuery(['foo', 'bar'], () => 'data', { queryKey: ['todos'], staleTime: 1000 })
80+
queryClient.fetchQuery(['foo', 'bar'], function myFn() { return 'data' }, { queryKey: ['todos'], staleTime: 1000 })
81+
queryClient.fetchQuery({ queryKey: ['foo', 'bar'], queryFn: () => 'data', retry: true })
82+
83+
const queryCache = queryClient.getQueryCache()
84+
85+
queryCache.find(['foo', 'bar'])
86+
queryCache.find(['foo', 'bar'], { queryKey: ['todos'], type: 'all', exact: true })
87+
queryCache.find(['foo', 'bar'], { type: 'all', exact: true })
88+
89+
queryCache.findAll(['foo', 'bar'])
90+
queryCache.findAll(['foo', 'bar'], { type: 'all', exact: true })
91+
queryCache.findAll(['foo', 'bar'], { queryKey: ['todos'], type: 'all', exact: true })
92+
queryCache.findAll({ queryKey: ['foo', 'bar'], type: 'all', exact: true })
93+
94+
return <div>Example Component</div>
95+
}
96+
97+
const globalQueryKey = ['module', 'level']
98+
99+
export const WithIdentifiers = () => {
100+
const queryKey = ['foo', 'bar']
101+
const mutationKey = ['posts', 'articles']
102+
const filters = { type: 'all', exact: true } as const
103+
const options = { context: undefined } as const
104+
const mutationOptions = { exact: true, fetching: false } as const
105+
const cancelOptions = { silent: true } as const
106+
const invalidateOptions = { cancelRefetch: true, throwOnError: true } as const
107+
const refetchOptions = { cancelRefetch: false, throwOnError: true } as const
108+
const resetOptions = { cancelRefetch: false, throwOnError: true } as const
109+
const fetchOptions = { queryFn: () => 'data', retry: true } as const
110+
const queryFn = () => 'data'
111+
112+
useIsFetching(queryKey)
113+
useIsFetching(queryKey, filters)
114+
useIsFetching(queryKey, filters, options)
115+
useIsFetching(queryKey, { type: 'all', exact: true })
116+
useIsFetching(queryKey, { type: 'all', exact: true }, { context: undefined })
117+
useIsFetching(queryKey, { queryKey: ['todos'], ...filters }, options)
118+
useIsFetching({ queryKey: queryKey, ...filters })
119+
useIsFetching({ queryKey: queryKey, ...filters }, { context: undefined })
120+
121+
useIsMutating(mutationKey)
122+
useIsMutating(mutationKey, { exact: true, status: 'idle' })
123+
useIsMutating(mutationKey, { ...mutationOptions, exact: false })
124+
useIsMutating({ mutationKey, ...mutationOptions })
125+
useIsMutating({ mutationKey: ['foo', 'bar'], exact: true, status: 'idle' })
126+
127+
// QueryClient methods
128+
// --- Instantiated hook call.
129+
const queryClient = useQueryClient()
130+
queryClient.cancelQueries(queryKey)
131+
queryClient.cancelQueries(queryKey, filters)
132+
queryClient.cancelQueries(queryKey, filters, cancelOptions)
133+
queryClient.cancelQueries(queryKey, { type: 'all', exact: true })
134+
queryClient.cancelQueries(queryKey, { type: 'all', exact: true }, { revert: true })
135+
queryClient.cancelQueries(queryKey, { queryKey: ['todos'], ...filters }, cancelOptions)
136+
queryClient.cancelQueries({ queryKey: queryKey, type: 'all', exact: true })
137+
queryClient.cancelQueries({ queryKey: ['foo', 'bar'], ...filters }, cancelOptions)
138+
139+
queryClient.getQueriesData(globalQueryKey)
140+
queryClient.getQueriesData({ queryKey: globalQueryKey, ...filters })
141+
queryClient.getQueriesData({ queryKey: ['foo', 'bar'], type: 'all' })
142+
143+
queryClient.invalidateQueries(queryKey)
144+
queryClient.invalidateQueries(queryKey, filters)
145+
queryClient.invalidateQueries(queryKey, filters, invalidateOptions)
146+
queryClient.invalidateQueries(queryKey, { queryKey: ['todos'], stale: true, ...filters })
147+
queryClient.invalidateQueries(queryKey, { queryKey: ['todos'], stale: true, ...filters }, invalidateOptions)
148+
queryClient.invalidateQueries({ queryKey: globalQueryKey, ...filters, stale: true })
149+
queryClient.invalidateQueries({ queryKey: globalQueryKey, ...filters, stale: true }, invalidateOptions)
150+
151+
queryClient.isFetching(globalQueryKey)
152+
queryClient.isFetching(globalQueryKey, filters)
153+
queryClient.isFetching(globalQueryKey, { queryKey: ['todos'], type: 'all', exact: true })
154+
queryClient.isFetching(globalQueryKey, { queryKey: ['todos'], ...filters })
155+
queryClient.isFetching({ queryKey: globalQueryKey, ...filters, stale: true })
156+
// Stays as it is because the code couldn't infer the type of the "queryKeysFromAnotherModule" identifier.
157+
queryClient.isFetching(queryKeysFromAnotherModule)
158+
159+
queryClient.refetchQueries(queryKey)
160+
queryClient.refetchQueries(queryKey, filters)
161+
queryClient.refetchQueries(queryKey, filters, refetchOptions)
162+
queryClient.refetchQueries(queryKey, { queryKey: ['todos'], ...filters }, { ...refetchOptions, cancelRefetch: true })
163+
queryClient.refetchQueries({ queryKey: queryKey, ...filters })
164+
queryClient.refetchQueries({ queryKey: queryKey, ...filters }, { ...refetchOptions, cancelRefetch: true })
165+
// Stays as it is because the code couldn't infer the type of the "queryKeysFromAnotherModule" identifier.
166+
queryClient.refetchQueries(queryKeysFromAnotherModule)
167+
queryClient.refetchQueries(queryKeysFromAnotherModule, filters)
168+
queryClient.refetchQueries(queryKeysFromAnotherModule, filters, refetchOptions)
169+
170+
queryClient.removeQueries(queryKey)
171+
queryClient.removeQueries(queryKey, filters)
172+
queryClient.removeQueries(queryKey, { queryKey: ['todos'], ...filters, stale: true })
173+
queryClient.removeQueries({ queryKey, ...filters, stale: true })
174+
// Stays as it is because the code couldn't infer the type of the "queryKeysFromAnotherModule" identifier.
175+
queryClient.removeQueries(queryKeysFromAnotherModule)
176+
queryClient.removeQueries(queryKeysFromAnotherModule, filters)
177+
178+
queryClient.resetQueries(queryKey)
179+
queryClient.resetQueries(queryKey, filters)
180+
queryClient.resetQueries(queryKey, filters, resetOptions)
181+
queryClient.resetQueries(queryKey, { queryKey: ['todos'], ...filters, stale: true })
182+
queryClient.resetQueries(queryKey, { queryKey: ['todos'], ...filters, stale: true }, resetOptions)
183+
queryClient.resetQueries({ queryKey, ...filters, stale: true })
184+
queryClient.resetQueries({ queryKey, ...filters, stale: true }, resetOptions)
185+
// Stays as it is because the code couldn't infer the type of the "queryKeysFromAnotherModule" identifier.
186+
queryClient.resetQueries(queryKeysFromAnotherModule)
187+
queryClient.resetQueries(queryKeysFromAnotherModule, filters)
188+
queryClient.resetQueries(queryKeysFromAnotherModule, filters, resetOptions)
189+
190+
queryClient.fetchQuery(queryKey)
191+
queryClient.fetchQuery(queryKey, fetchOptions)
192+
queryClient.fetchQuery(queryKey, { networkMode: 'always', ...fetchOptions })
193+
queryClient.fetchQuery(queryKey, queryFn, fetchOptions)
194+
queryClient.fetchQuery(queryKey, () => 'data', { networkMode: 'always', ...fetchOptions })
195+
// Stays as it is because the code couldn't infer the type of the "queryKeysFromAnotherModule" identifier.
196+
queryClient.fetchQuery(queryKeysFromAnotherModule)
197+
queryClient.fetchQuery(queryKeysFromAnotherModule, fetchOptions)
198+
queryClient.fetchQuery(queryKeysFromAnotherModule, queryFn, fetchOptions)
199+
}

0 commit comments

Comments
 (0)