Skip to content

Commit eb21846

Browse files
committed
feat: add QueryCache.fetchQuery method
1 parent 6a6a657 commit eb21846

File tree

12 files changed

+192
-135
lines changed

12 files changed

+192
-135
lines changed

docs/src/pages/docs/api.md

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ const {
2424
} = useQuery(queryKey, queryFn?, {
2525
cacheTime,
2626
enabled,
27-
forceFetchOnMount,
2827
initialData,
2928
initialStale,
3029
isDataEqual,
@@ -142,10 +141,6 @@ const queryInfo = useQuery({
142141
- Optional
143142
- Defaults to `false`
144143
- If set, any previous `data` will be kept when fetching new data because the query key changed.
145-
- `forceFetchOnMount: Boolean`
146-
- Optional
147-
- Defaults to `false`
148-
- Set this to `true` to always fetch when the component mounts (regardless of staleness).
149144
- `queryFnParamsFilter: Function(args) => filteredArgs`
150145
- Optional
151146
- This function will filter the params that get passed to `queryFn`.
@@ -362,6 +357,7 @@ const queryCache = new QueryCache({
362357
363358
Its available properties and methods are:
364359
360+
- [`fetchQuery`](#querycachefetchquery)
365361
- [`prefetchQuery`](#querycacheprefetchquery)
366362
- [`getQueryData`](#querycachegetquerydata)
367363
- [`setQueryData`](#querycachesetquerydata)
@@ -380,9 +376,25 @@ Its available properties and methods are:
380376
- Optional
381377
- Define defaults for all queries and mutations using this query cache.
382378
379+
## `queryCache.fetchQuery`
380+
381+
`fetchQuery` is an asynchronous method that can be used to fetch and cache a query. It will either resolve with the data or throw with the error. Specify a `staleTime` to only trigger a fetch when the data is stale. Use the `prefetchQuery` method if you just want to fetch a query without needing the result.
382+
383+
```js
384+
try {
385+
const data = await queryCache.fetchQuery(queryKey, queryFn)
386+
} catch (error) {
387+
console.log(error)
388+
}
389+
```
390+
391+
**Returns**
392+
393+
- `Promise<TResult>`
394+
383395
## `queryCache.prefetchQuery`
384396
385-
`prefetchQuery` is an asynchronous function that can be used to fetch and cache a query response before it is needed or rendered with `useQuery` and friends.
397+
`prefetchQuery` is an asynchronous method that can be used to fetch and cache a query response before it is needed or rendered with `useQuery` and friends.
386398
387399
- If either:
388400
- The query does not exist or
@@ -394,13 +406,13 @@ Its available properties and methods are:
394406
> The difference between using `prefetchQuery` and `setQueryData` is that `prefetchQuery` is async and will ensure that duplicate requests for this query are not created with `useQuery` instances for the same query are rendered while the data is fetching.
395407
396408
```js
397-
const data = await queryCache.prefetchQuery(queryKey, queryFn)
409+
await queryCache.prefetchQuery(queryKey, queryFn)
398410
```
399411
400412
To pass options like `force` or `throwOnError`, use the fourth options object:
401413
402414
```js
403-
const data = await queryCache.prefetchQuery(queryKey, queryFn, config, {
415+
await queryCache.prefetchQuery(queryKey, queryFn, config, {
404416
force: true,
405417
throwOnError: true,
406418
})
@@ -409,7 +421,7 @@ const data = await queryCache.prefetchQuery(queryKey, queryFn, config, {
409421
You can even use it with a default queryFn in your config!
410422
411423
```js
412-
const data = await queryCache.prefetchQuery(queryKey)
424+
await queryCache.prefetchQuery(queryKey)
413425
```
414426
415427
**Options**
@@ -423,7 +435,7 @@ The options for `prefetchQuery` are exactly the same as those of [`useQuery`](#u
423435
424436
**Returns**
425437
426-
- `promise: Promise`
438+
- `Promise<TResult | undefined>`
427439
- A promise is returned that will either immediately resolve with the query's cached response data, or resolve to the data returned by the fetch function. It **will not** throw an error if the fetch fails. This can be configured by setting the `throwOnError` option to `true`.
428440
429441
## `queryCache.getQueryData`

docs/src/pages/docs/guides/prefetching.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@ id: prefetching
33
title: Prefetching
44
---
55

6-
If you're lucky enough, you may know enough about what your users will do to be able to prefetch the data they need before it's needed! If this is the case, you can use the `prefetchQuery` function to prefetch the results of a query to be placed into the cache:
6+
If you're lucky enough, you may know enough about what your users will do to be able to prefetch the data they need before it's needed! If this is the case, you can use the `fetchQuery` or `prefetchQuery` methods to prefetch the results of a query to be placed into the cache:
77

88
```js
99
const prefetchTodos = async () => {
10-
const queryData = await queryCache.prefetchQuery('todos', () =>
11-
fetch('/todos')
12-
)
10+
await queryCache.prefetchQuery('todos', () => fetch('/todos'))
1311
// The results of this query will be cached like a normal query
1412
}
1513
```

docs/src/pages/docs/guides/ssr.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,12 @@ Since there are many different possible setups for SSR, it's hard to give a deta
118118
> Note: The global `queryCache` you can import directly from 'react-query' does not cache queries on the server to avoid leaking sensitive information between requests.
119119
120120
- Prefetch data
121-
- Create a `prefetchQueryCache` specifically for prefetching by calling `const prefetchQueryCache = new QueryCache()`
122-
- Call `prefetchQueryCache.prefetchQuery(...)` to prefetch queries
123-
- Dehydrate by using `const dehydratedState = dehydrate(prefetchQueryCache)`
121+
- Create a `prefetchCache` specifically for prefetching by calling `const prefetchCache = new QueryCache()`
122+
- Call `prefetchCache.prefetchQuery(...)` to prefetch queries
123+
- Dehydrate by using `const dehydratedState = dehydrate(prefetchCache)`
124124
- Render
125-
- Create a new render query cache and hydrate the state. Use this query cache to render your app.
126-
- **Do not** use the `prefetchQueryCache` to render your app, the server and client both needs to render from the dehydrated data to avoid React hydration mismatches. This is because queries with errors are excluded from dehydration by default.
125+
- Create a new query cache for rendering and hydrate the state. Use this query cache to render your app.
126+
- **Do not** use the `prefetchCache` to render your app, the server and client both needs to render from the dehydrated data to avoid React hydration mismatches. This is because queries with errors are excluded from dehydration by default.
127127
- Serialize and embed `dehydratedState` in the markup
128128
- Security note: Serializing data with `JSON.stringify` can put you at risk for XSS-vulnerabilities, [this blog post explains why and how to solve it](https://medium.com/node-security/the-most-common-xss-vulnerability-in-react-js-applications-2bdffbcc1fa0)
129129

@@ -180,7 +180,7 @@ ReactDOM.hydrate(
180180

181181
Any query with an error is automatically excluded from dehydration. This means that the default behaviour is to pretend these queries were never loaded on the server, usually showing a loading state instead, and retrying the queries on the client. This happens regardless of error.
182182

183-
Sometimes this behavior is not desirable, maybe you want to render an error page with a correct status code instead on certain errors or queries. In those cases, pass `throwOnError: true` to the specific `prefetchQuery` to be able to catch and handle those errors manually.
183+
Sometimes this behavior is not desirable, maybe you want to render an error page with a correct status code instead on certain errors or queries. In those cases, use `fetchQuery` and catch any errors to handle those manually.
184184

185185
**Staleness is measured from when the query was fetched on the server**
186186

src/core/query.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ export class Query<TResult, TError> {
110110
cacheTime: number
111111

112112
private queryCache: QueryCache
113-
private promise?: Promise<TResult | undefined>
113+
private promise?: Promise<TResult>
114114
private gcTimeout?: number
115115
private cancelFetch?: (silent?: boolean) => void
116116
private continueFetch?: () => void
@@ -160,12 +160,15 @@ export class Query<TResult, TError> {
160160
}, this.cacheTime)
161161
}
162162

163-
async cancel(silent?: boolean): Promise<void> {
163+
cancel(silent?: boolean): Promise<undefined> {
164164
const promise = this.promise
165+
165166
if (promise && this.cancelFetch) {
166167
this.cancelFetch(silent)
167-
await promise.catch(noop)
168+
return promise.then(noop).catch(noop)
168169
}
170+
171+
return Promise.resolve(undefined)
169172
}
170173

171174
private continue(): void {
@@ -316,17 +319,17 @@ export class Query<TResult, TError> {
316319
}
317320
}
318321

319-
async refetch(
322+
refetch(
320323
options?: RefetchOptions,
321324
config?: ResolvedQueryConfig<TResult, TError>
322325
): Promise<TResult | undefined> {
323-
try {
324-
return await this.fetch(undefined, config)
325-
} catch (error) {
326-
if (options?.throwOnError === true) {
327-
throw error
328-
}
326+
let promise: Promise<TResult | undefined> = this.fetch(undefined, config)
327+
328+
if (!options?.throwOnError) {
329+
promise = promise.catch(noop)
329330
}
331+
332+
return promise
330333
}
331334

332335
fetchMore(
@@ -348,7 +351,7 @@ export class Query<TResult, TError> {
348351
async fetch(
349352
options?: FetchOptions,
350353
config?: ResolvedQueryConfig<TResult, TError>
351-
): Promise<TResult | undefined> {
354+
): Promise<TResult> {
352355
if (this.promise) {
353356
if (options?.fetchMore && this.state.data) {
354357
// Silently cancel current fetch if the user wants to fetch more

src/core/queryCache.ts

Lines changed: 88 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
isOnline,
77
isPlainObject,
88
isServer,
9+
noop,
910
} from './utils'
1011
import { getResolvedQueryConfig } from './config'
1112
import { Query } from './query'
@@ -55,6 +56,12 @@ type QueryPredicate = QueryKey | QueryPredicateFn | true
5556

5657
type QueryPredicateFn = (query: Query<unknown, unknown>) => boolean
5758

59+
export interface FetchQueryObjectConfig<TResult, TError> {
60+
queryKey: QueryKey
61+
queryFn?: QueryFunction<TResult>
62+
config?: QueryConfig<TResult, TError>
63+
}
64+
5865
export interface PrefetchQueryObjectConfig<TResult, TError> {
5966
queryKey: QueryKey
6067
queryFn?: QueryFunction<TResult>
@@ -302,87 +309,135 @@ export class QueryCache {
302309
return query
303310
}
304311

312+
// Parameter syntax
313+
fetchQuery<TResult = unknown, TError = unknown>(
314+
queryKey: QueryKey,
315+
queryConfig?: QueryConfig<TResult, TError>
316+
): Promise<TResult>
317+
318+
// Parameter syntax with query function
319+
fetchQuery<TResult, TError, TArgs extends TypedQueryFunctionArgs>(
320+
queryKey: QueryKey,
321+
queryFn: TypedQueryFunction<TResult, TArgs>,
322+
queryConfig?: QueryConfig<TResult, TError>
323+
): Promise<TResult>
324+
325+
fetchQuery<TResult = unknown, TError = unknown>(
326+
queryKey: QueryKey,
327+
queryFn: QueryFunction<TResult>,
328+
queryConfig?: QueryConfig<TResult, TError>
329+
): Promise<TResult>
330+
331+
// Object syntax
332+
fetchQuery<TResult = unknown, TError = unknown>(
333+
config: FetchQueryObjectConfig<TResult, TError>
334+
): Promise<TResult>
335+
336+
// Implementation
337+
fetchQuery<TResult, TError>(
338+
arg1: any,
339+
arg2?: any,
340+
arg3?: any
341+
): Promise<TResult> {
342+
const [queryKey, config] = getQueryArgs<TResult, TError>(arg1, arg2, arg3)
343+
344+
const resolvedConfig = this.getResolvedQueryConfig(queryKey, {
345+
// https://github.com/tannerlinsley/react-query/issues/652
346+
retry: false,
347+
...config,
348+
})
349+
350+
let query = this.getQueryByHash<TResult, TError>(resolvedConfig.queryHash)
351+
352+
if (!query) {
353+
query = this.createQuery(resolvedConfig)
354+
}
355+
356+
if (!query.isStaleByTime(config.staleTime)) {
357+
return Promise.resolve(query.state.data as TResult)
358+
}
359+
360+
return query.fetch(undefined, resolvedConfig)
361+
}
362+
363+
/**
364+
* @deprecated
365+
*/
305366
// Parameter syntax with optional prefetch options
306-
async prefetchQuery<TResult = unknown, TError = unknown>(
367+
prefetchQuery<TResult = unknown, TError = unknown>(
307368
queryKey: QueryKey,
308369
options?: PrefetchQueryOptions
309370
): Promise<TResult | undefined>
310371

311372
// Parameter syntax with query function and optional prefetch options
312-
async prefetchQuery<TResult, TError, TArgs extends TypedQueryFunctionArgs>(
373+
prefetchQuery<TResult, TError, TArgs extends TypedQueryFunctionArgs>(
313374
queryKey: QueryKey,
314375
queryFn: TypedQueryFunction<TResult, TArgs>,
315376
options?: PrefetchQueryOptions
316377
): Promise<TResult | undefined>
317378

318-
async prefetchQuery<TResult = unknown, TError = unknown>(
379+
prefetchQuery<TResult = unknown, TError = unknown>(
319380
queryKey: QueryKey,
320381
queryFn: QueryFunction<TResult>,
321382
options?: PrefetchQueryOptions
322383
): Promise<TResult | undefined>
323384

324385
// Parameter syntax with query function, config and optional prefetch options
325-
async prefetchQuery<TResult, TError, TArgs extends TypedQueryFunctionArgs>(
386+
prefetchQuery<TResult, TError, TArgs extends TypedQueryFunctionArgs>(
326387
queryKey: QueryKey,
327388
queryFn: TypedQueryFunction<TResult, TArgs>,
328389
queryConfig: QueryConfig<TResult, TError>,
329390
options?: PrefetchQueryOptions
330391
): Promise<TResult | undefined>
331392

332-
async prefetchQuery<TResult = unknown, TError = unknown>(
393+
prefetchQuery<TResult = unknown, TError = unknown>(
333394
queryKey: QueryKey,
334395
queryFn: QueryFunction<TResult>,
335396
queryConfig: QueryConfig<TResult, TError>,
336397
options?: PrefetchQueryOptions
337398
): Promise<TResult | undefined>
338399

339400
// Object syntax
340-
async prefetchQuery<TResult = unknown, TError = unknown>(
401+
prefetchQuery<TResult = unknown, TError = unknown>(
341402
config: PrefetchQueryObjectConfig<TResult, TError>
342403
): Promise<TResult | undefined>
343404

344405
// Implementation
345-
async prefetchQuery<TResult, TError>(
346-
...args: any[]
406+
prefetchQuery<TResult, TError>(
407+
arg1: any,
408+
arg2?: any,
409+
arg3?: any,
410+
arg4?: any
347411
): Promise<TResult | undefined> {
348412
if (
349-
isPlainObject(args[1]) &&
350-
(args[1].hasOwnProperty('throwOnError') ||
351-
args[1].hasOwnProperty('force'))
413+
isPlainObject(arg2) &&
414+
(arg2.hasOwnProperty('throwOnError') || arg2.hasOwnProperty('force'))
352415
) {
353-
args[3] = args[1]
354-
args[1] = undefined
355-
args[2] = undefined
416+
arg4 = arg2
417+
arg2 = undefined
418+
arg3 = undefined
356419
}
357420

358421
const [queryKey, config, options] = getQueryArgs<
359422
TResult,
360423
TError,
361424
PrefetchQueryOptions | undefined
362-
>(args)
425+
>(arg1, arg2, arg3, arg4)
363426

364-
const resolvedConfig = this.getResolvedQueryConfig(queryKey, {
365-
// https://github.com/tannerlinsley/react-query/issues/652
366-
retry: false,
367-
...config,
368-
})
427+
if (options?.force) {
428+
config.staleTime = 0
429+
}
369430

370-
let query = this.getQueryByHash<TResult, TError>(resolvedConfig.queryHash)
431+
let promise: Promise<TResult | undefined> = this.fetchQuery(
432+
queryKey,
433+
config
434+
)
371435

372-
if (!query) {
373-
query = this.createQuery(resolvedConfig)
436+
if (!options?.throwOnError) {
437+
promise = promise.catch(noop)
374438
}
375439

376-
try {
377-
if (options?.force || query.isStaleByTime(config.staleTime)) {
378-
await query.fetch(undefined, resolvedConfig)
379-
}
380-
return query.state.data
381-
} catch (error) {
382-
if (options?.throwOnError) {
383-
throw error
384-
}
385-
}
440+
return promise
386441
}
387442

388443
setQueryData<TResult, TError = unknown>(

0 commit comments

Comments
 (0)