Skip to content

Commit 985c564

Browse files
committed
Merge remote-tracking branch 'react-query/alpha' into feature/persistQueryClient
2 parents 5c4de91 + 239d66c commit 985c564

File tree

12 files changed

+265
-68
lines changed

12 files changed

+265
-68
lines changed

docs/src/pages/guides/migrating-to-react-query-4.md

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -217,18 +217,9 @@ The `useQueries` hook now accepts an object with a `queries` prop as its input.
217217
```
218218

219219

220-
### Removed undocumented methods from the `queryClient` and `query`
220+
### Removed undocumented methods from the `queryClient`, `query` and `mutation`
221221

222-
The methods `cancelMutatations` and `executeMutation` on the `QueryClient` were undocumented and unused internally, so we removed them. Since they were just wrappers around methods available on the `mutationCache`, you can still use the functionality.
223-
224-
```diff
225-
- cancelMutations(): Promise<void> {
226-
- const promises = notifyManager.batch(() =>
227-
- this.mutationCache.getAll().map(mutation => mutation.cancel())
228-
- )
229-
- return Promise.all(promises).then(noop).catch(noop)
230-
- }
231-
```
222+
The methods `cancelMutatations` and `executeMutation` on the `QueryClient` were undocumented and unused internally, so we removed them. Since it was just a wrapper around a method available on the `mutationCache`, you can still use the functionality of `executeMutation`
232223

233224
```diff
234225
- executeMutation<
@@ -243,7 +234,7 @@ The methods `cancelMutatations` and `executeMutation` on the `QueryClient` were
243234
- }
244235
```
245236

246-
Additionally, `query.setDefaultOptions` was removed because it was also unused.
237+
Additionally, `query.setDefaultOptions` was removed because it was also unused. `mutation.cancel` was removed because it didn't actually cancel the outgoing request.
247238

248239
### TypeScript
249240

docs/src/pages/guides/query-cancellation.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,18 @@ const query = useQuery(['todos'], ({ signal }) => {
9999
100100
## Using `graphql-request`
101101
102+
An `AbortSignal` can be set in the client `request` method.
103+
104+
```js
105+
const client = new GraphQLClient(endpoint)
106+
107+
const query = useQuery('todos', ({ signal }) => {
108+
client.request({ document: query, signal })
109+
})
110+
```
111+
112+
## Using `graphql-request` version less than v4.0.0
113+
102114
An `AbortSignal` can be set in the `GraphQLClient` constructor.
103115
104116
```js

docs/src/pages/reference/QueryClient.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ This distinction is more a "convenience" for ts devs that know which structure w
203203

204204
## `queryClient.setQueryData`
205205

206-
`setQueryData` is a synchronous function that can be used to immediately update a query's cached data. If the query does not exist, it will be created. **If the query is not utilized by a query hook in the default `cacheTime` of 5 minutes, the query will be garbage collected**.
206+
`setQueryData` is a synchronous function that can be used to immediately update a query's cached data. If the query does not exist, it will be created. **If the query is not utilized by a query hook in the default `cacheTime` of 5 minutes, the query will be garbage collected**. To update multiple queries at once and match query keys partially, you need to use [`queryClient.setQueriesData`](#queryclientsetqueriesdata) instead.
207207

208208
> The difference between using `setQueryData` and `fetchQuery` is that `setQueryData` is sync and assumes that you already synchronously have the data available. If you need to fetch the data asynchronously, it's suggested that you either refetch the query key or use `fetchQuery` to handle the asynchronous fetch.
209209
@@ -248,7 +248,7 @@ console.log(state.dataUpdatedAt)
248248

249249
## `queryClient.setQueriesData`
250250

251-
`setQueriesData` is a synchronous function that can be used to immediately update cached data of multiple queries. Only queries that match the passed queryKey or queryFilter will be updated - no new cache entries will be created. Under the hood, [`setQueryData`](#queryclientsetquerydata) is called for each query.
251+
`setQueriesData` is a synchronous function that can be used to immediately update cached data of multiple queries by using filter function or partially matching the query key. Only queries that match the passed queryKey or queryFilter will be updated - no new cache entries will be created. Under the hood, [`setQueryData`](#queryclientsetquerydata) is called for each query.
252252

253253
```js
254254
queryClient.setQueriesData(queryKey | filters, updater)
@@ -257,7 +257,7 @@ queryClient.setQueriesData(queryKey | filters, updater)
257257
**Options**
258258

259259
- `queryKey: QueryKey`: [Query Keys](../guides/query-keys) | `filters: QueryFilters`: [Query Filters](../guides/filters#query-filters)
260-
- if a queryKey is passed as first argument, queryKeys fuzzily matching this param will be updated
260+
- if a queryKey is passed as first argument, queryKeys partially matching this param will be updated
261261
- if a filter is passed, queryKeys matching the filter will be updated
262262
- `updater: TData | (oldData: TData | undefined) => TData`
263263
- the [setQueryData](#queryclientsetquerydata) updater function or new data, will be called for each matching queryKey
@@ -474,6 +474,9 @@ The `getQueryDefaults` method returns the default options which have been set fo
474474
const defaultOptions = queryClient.getQueryDefaults(['posts'])
475475
```
476476

477+
> Note that if several query defaults match the given query key, the **first** matching one is returned.
478+
> This could lead to unexpected behaviours. See [`setQueryDefaults`](#queryclientsetquerydefaults).
479+
477480
## `queryClient.setQueryDefaults`
478481

479482
`setQueryDefaults` can be used to set default options for specific queries:
@@ -491,6 +494,9 @@ function Component() {
491494
- `queryKey: QueryKey`: [Query Keys](../guides/query-keys)
492495
- `options: QueryOptions`
493496

497+
> As stated in [`getQueryDefaults`](#queryclientgetquerydefaults), the order of registration of query defaults does matter.
498+
> Since the **first** matching defaults are returned by `getQueryDefaults`, the registration should be made in the following order: from the **least generic key** to the **most generic one**. This way, in case of specific key, the first matching one would be the expected one.
499+
494500
## `queryClient.getMutationDefaults`
495501

496502
The `getMutationDefaults` method returns the default options which have been set for specific mutations:
@@ -516,6 +522,8 @@ function Component() {
516522
- `mutationKey: string | unknown[]`
517523
- `options: MutationOptions`
518524

525+
> Similar to [`setQueryDefaults`](#queryclientsetquerydefaults), the order of registration does matter here.
526+
519527
## `queryClient.getQueryCache`
520528

521529
The `getQueryCache` method returns the query cache this client is connected to.

examples/load-more-infinite-scroll/pages/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ function Example() {
4848
useIntersectionObserver({
4949
target: loadMoreButtonRef,
5050
onIntersect: fetchNextPage,
51-
enabled: hasNextPage,
51+
enabled: !!hasNextPage,
5252
})
5353

5454
return (

src/core/mutation.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { getLogger } from './logger'
55
import { notifyManager } from './notifyManager'
66
import { Removable } from './removable'
77
import { canFetch, Retryer, createRetryer } from './retryer'
8-
import { noop } from './utils'
98

109
// TYPES
1110

@@ -150,14 +149,6 @@ export class Mutation<
150149
}
151150
}
152151

153-
cancel(): Promise<void> {
154-
if (this.retryer) {
155-
this.retryer.cancel()
156-
return this.retryer.promise.then(noop).catch(noop)
157-
}
158-
return Promise.resolve()
159-
}
160-
161152
continue(): Promise<TData> {
162153
if (this.retryer) {
163154
this.retryer.continue()

src/core/mutationCache.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,6 @@ export class MutationCache extends Subscribable<MutationCacheListener> {
106106

107107
remove(mutation: Mutation<any, any, any, any>): void {
108108
this.mutations = this.mutations.filter(x => x !== mutation)
109-
mutation.cancel()
110109
this.notify({ type: 'removed', mutation })
111110
}
112111

src/core/queryClient.ts

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import { onlineManager } from './onlineManager'
3838
import { notifyManager } from './notifyManager'
3939
import { infiniteQueryBehavior } from './infiniteQueryBehavior'
4040
import { CancelOptions, DefaultedQueryObserverOptions } from './types'
41+
import { getLogger } from './logger'
4142

4243
// TYPES
4344

@@ -535,10 +536,32 @@ export class QueryClient {
535536
getQueryDefaults(
536537
queryKey?: QueryKey
537538
): QueryObserverOptions<any, any, any, any, any> | undefined {
538-
return queryKey
539-
? this.queryDefaults.find(x => partialMatchKey(queryKey, x.queryKey))
540-
?.defaultOptions
541-
: undefined
539+
if (!queryKey) {
540+
return undefined
541+
}
542+
543+
// Get the first matching defaults
544+
const firstMatchingDefaults = this.queryDefaults.find(x =>
545+
partialMatchKey(queryKey, x.queryKey)
546+
)
547+
548+
// Additional checks and error in dev mode
549+
if (process.env.NODE_ENV !== 'production') {
550+
// Retrieve all matching defaults for the given key
551+
const matchingDefaults = this.queryDefaults.filter(x =>
552+
partialMatchKey(queryKey, x.queryKey)
553+
)
554+
// It is ok not having defaults, but it is error prone to have more than 1 default for a given key
555+
if (matchingDefaults.length > 1) {
556+
getLogger().error(
557+
`[QueryClient] Several query defaults match with key '${JSON.stringify(
558+
queryKey
559+
)}'. The first matching query defaults are used. Please check how query defaults are registered. Order does matter here. cf. https://react-query.tanstack.com/reference/QueryClient#queryclientsetquerydefaults.`
560+
)
561+
}
562+
}
563+
564+
return firstMatchingDefaults?.defaultOptions
542565
}
543566

544567
setMutationDefaults(
@@ -558,11 +581,32 @@ export class QueryClient {
558581
getMutationDefaults(
559582
mutationKey?: MutationKey
560583
): MutationObserverOptions<any, any, any, any> | undefined {
561-
return mutationKey
562-
? this.mutationDefaults.find(x =>
563-
partialMatchKey(mutationKey, x.mutationKey)
564-
)?.defaultOptions
565-
: undefined
584+
if (!mutationKey) {
585+
return undefined
586+
}
587+
588+
// Get the first matching defaults
589+
const firstMatchingDefaults = this.mutationDefaults.find(x =>
590+
partialMatchKey(mutationKey, x.mutationKey)
591+
)
592+
593+
// Additional checks and error in dev mode
594+
if (process.env.NODE_ENV !== 'production') {
595+
// Retrieve all matching defaults for the given key
596+
const matchingDefaults = this.mutationDefaults.filter(x =>
597+
partialMatchKey(mutationKey, x.mutationKey)
598+
)
599+
// It is ok not having defaults, but it is error prone to have more than 1 default for a given key
600+
if (matchingDefaults.length > 1) {
601+
getLogger().error(
602+
`[QueryClient] Several mutation defaults match with key '${JSON.stringify(
603+
mutationKey
604+
)}'. The first matching mutation defaults are used. Please check how mutation defaults are registered. Order does matter here. cf. https://react-query.tanstack.com/reference/QueryClient#queryclientsetmutationdefaults.`
605+
)
606+
}
607+
}
608+
609+
return firstMatchingDefaults?.defaultOptions
566610
}
567611

568612
defaultQueryOptions<

src/core/tests/mutations.test.tsx

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -369,33 +369,6 @@ describe('mutations', () => {
369369
consoleMock.mockRestore()
370370
})
371371

372-
test('cancel mutation should not call mutationFn if the current retrier is undefined', async () => {
373-
const mutationFn = jest.fn().mockImplementation(async () => {
374-
await sleep(20)
375-
return 'data'
376-
})
377-
378-
const observer = new MutationObserver(queryClient, {
379-
mutationKey: ['key'],
380-
mutationFn,
381-
})
382-
383-
observer.mutate()
384-
const mutation = queryClient
385-
.getMutationCache()
386-
.find({ mutationKey: ['key'] })!
387-
await sleep(10)
388-
389-
// Force current mutation retryer to be undefined
390-
// because not use case has been found
391-
mutation['retryer'] = undefined
392-
mutationFn.mockReset()
393-
await mutation.cancel()
394-
395-
await sleep(30)
396-
expect(mutationFn).toHaveBeenCalledTimes(0)
397-
})
398-
399372
test('reducer should return the state for an unknown action type', async () => {
400373
const observer = new MutationObserver(queryClient, {
401374
mutationKey: ['key'],

src/core/tests/queryClient.test.tsx

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,132 @@ describe('queryClient', () => {
130130
queryClient.setQueryDefaults(key, queryOptions2)
131131
expect(queryClient.getQueryDefaults(key)).toMatchObject(queryOptions2)
132132
})
133+
134+
test('should warn in dev if several query defaults match a given key', () => {
135+
// Check discussion here: https://github.com/tannerlinsley/react-query/discussions/3199
136+
const consoleErrorMock = jest.spyOn(console, 'error')
137+
consoleErrorMock.mockImplementation(() => true)
138+
139+
const keyABCD = [
140+
{
141+
a: 'a',
142+
b: 'b',
143+
c: 'c',
144+
d: 'd',
145+
},
146+
]
147+
148+
// The key below "contains" keyABCD => it is more generic
149+
const keyABC = [
150+
{
151+
a: 'a',
152+
b: 'b',
153+
c: 'c',
154+
},
155+
]
156+
157+
// The defaults for query matching key "ABCD" (least generic)
158+
const defaultsOfABCD = {
159+
queryFn: function ABCDQueryFn() {
160+
return 'ABCD'
161+
},
162+
}
163+
164+
// The defaults for query matching key "ABC" (most generic)
165+
const defaultsOfABC = {
166+
queryFn: function ABCQueryFn() {
167+
return 'ABC'
168+
},
169+
}
170+
171+
// No defaults, no warning
172+
const noDefaults = queryClient.getQueryDefaults(keyABCD)
173+
expect(noDefaults).toBeUndefined()
174+
expect(consoleErrorMock).not.toHaveBeenCalled()
175+
176+
// If defaults for key ABCD are registered **before** the ones of key ABC (more generic)…
177+
queryClient.setQueryDefaults(keyABCD, defaultsOfABCD)
178+
queryClient.setQueryDefaults(keyABC, defaultsOfABC)
179+
// … then the "good" defaults are retrieved: we get the ones for key "ABCD"
180+
const goodDefaults = queryClient.getQueryDefaults(keyABCD)
181+
expect(goodDefaults).toBe(defaultsOfABCD)
182+
// The warning is still raised since several defaults are matching
183+
expect(consoleErrorMock).toHaveBeenCalledTimes(1)
184+
185+
// Let's create another queryClient and change the order of registration
186+
const newQueryClient = new QueryClient()
187+
// The defaults for key ABC (more generic) are registered **before** the ones of key ABCD…
188+
newQueryClient.setQueryDefaults(keyABC, defaultsOfABC)
189+
newQueryClient.setQueryDefaults(keyABCD, defaultsOfABCD)
190+
// … then the "wrong" defaults are retrieved: we get the ones for key "ABC"
191+
const badDefaults = newQueryClient.getQueryDefaults(keyABCD)
192+
expect(badDefaults).not.toBe(defaultsOfABCD)
193+
expect(badDefaults).toBe(defaultsOfABC)
194+
expect(consoleErrorMock).toHaveBeenCalledTimes(2)
195+
196+
consoleErrorMock.mockRestore()
197+
})
198+
199+
test('should warn in dev if several mutation defaults match a given key', () => {
200+
// Check discussion here: https://github.com/tannerlinsley/react-query/discussions/3199
201+
const consoleErrorMock = jest.spyOn(console, 'error')
202+
consoleErrorMock.mockImplementation(() => true)
203+
204+
const keyABCD = [
205+
{
206+
a: 'a',
207+
b: 'b',
208+
c: 'c',
209+
d: 'd',
210+
},
211+
]
212+
213+
// The key below "contains" keyABCD => it is more generic
214+
const keyABC = [
215+
{
216+
a: 'a',
217+
b: 'b',
218+
c: 'c',
219+
},
220+
]
221+
222+
// The defaults for mutation matching key "ABCD" (least generic)
223+
const defaultsOfABCD = {
224+
mutationFn: Promise.resolve,
225+
}
226+
227+
// The defaults for mutation matching key "ABC" (most generic)
228+
const defaultsOfABC = {
229+
mutationFn: Promise.resolve,
230+
}
231+
232+
// No defaults, no warning
233+
const noDefaults = queryClient.getMutationDefaults(keyABCD)
234+
expect(noDefaults).toBeUndefined()
235+
expect(consoleErrorMock).not.toHaveBeenCalled()
236+
237+
// If defaults for key ABCD are registered **before** the ones of key ABC (more generic)…
238+
queryClient.setMutationDefaults(keyABCD, defaultsOfABCD)
239+
queryClient.setMutationDefaults(keyABC, defaultsOfABC)
240+
// … then the "good" defaults are retrieved: we get the ones for key "ABCD"
241+
const goodDefaults = queryClient.getMutationDefaults(keyABCD)
242+
expect(goodDefaults).toBe(defaultsOfABCD)
243+
// The warning is still raised since several defaults are matching
244+
expect(consoleErrorMock).toHaveBeenCalledTimes(1)
245+
246+
// Let's create another queryClient and change the order of registration
247+
const newQueryClient = new QueryClient()
248+
// The defaults for key ABC (more generic) are registered **before** the ones of key ABCD…
249+
newQueryClient.setMutationDefaults(keyABC, defaultsOfABC)
250+
newQueryClient.setMutationDefaults(keyABCD, defaultsOfABCD)
251+
// … then the "wrong" defaults are retrieved: we get the ones for key "ABC"
252+
const badDefaults = newQueryClient.getMutationDefaults(keyABCD)
253+
expect(badDefaults).not.toBe(defaultsOfABCD)
254+
expect(badDefaults).toBe(defaultsOfABC)
255+
expect(consoleErrorMock).toHaveBeenCalledTimes(2)
256+
257+
consoleErrorMock.mockRestore()
258+
})
133259
})
134260

135261
describe('setQueryData', () => {

src/reactjs/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ export * from './types'
2525
export type { QueryClientProviderProps } from './QueryClientProvider'
2626
export type { QueryErrorResetBoundaryProps } from './QueryErrorResetBoundary'
2727
export type { HydrateProps } from './Hydrate'
28+
export type { QueriesOptions, QueriesResults } from './useQueries'

0 commit comments

Comments
 (0)