Skip to content

Commit 1f17ca7

Browse files
committed
feat(rtk-query): prefetch thunk return type #1283
Description: prefetch thunk now returns an object with shape: ```ts export interface PrefetchActionCreatorResult { /** * Returns a promise that **always** resolves to `undefined` * when there are no pending requests initiated by input thunk. */ unwrap(): Promise<void>, /** * Cancels pending requests. */ abort(): void } ```
1 parent 29b7673 commit 1f17ca7

17 files changed

+275
-88
lines changed

docs/rtk-query/api/created-api/api-slice-utils.mdx

+18-3
Original file line numberDiff line numberDiff line change
@@ -155,13 +155,23 @@ patchCollection.undo()
155155
#### Signature
156156

157157
```ts no-transpile
158-
type PrefetchOptions = { ifOlderThan?: false | number } | { force?: boolean };
158+
interface PrefetchActionCreatorResult {
159+
unwrap(): Promise<void>,
160+
abort(): void
161+
}
162+
163+
type PrefetchOptions =
164+
| {
165+
ifOlderThan?: false | number,
166+
keepSubscriptionFor?: number
167+
}
168+
| { force?: boolean, keepSubscriptionFor?: number }
159169

160170
const prefetch = (
161171
endpointName: string,
162172
arg: any,
163173
options: PrefetchOptions
164-
) => ThunkAction<void, any, any, AnyAction>;
174+
) => ThunkAction<PrefetchActionCreatorResult, any, any, AnyAction>
165175
```
166176

167177
- **Parameters**
@@ -171,13 +181,18 @@ const prefetch = (
171181
- `options`: options to determine whether the request should be sent for a given situation:
172182
- `ifOlderThan`: if specified, only runs the query if the difference between `new Date()` and the last`fulfilledTimeStamp` is greater than the given value (in seconds)
173183
- `force`: if `true`, it will ignore the `ifOlderThan` value if it is set and the query will be run even if it exists in the cache.
174-
184+
- `keepSubscriptionFor`: how long in seconds before the data retrieved is considered unused;
185+
defaults to `api.config.keepPrefetchSubscriptionsFor`.
175186
#### Description
176187

177188
A Redux thunk action creator that can be used to manually trigger pre-fetching of data.
178189

179190
The thunk action creator accepts three arguments: the name of the endpoint we are updating (such as `'getPost'`), any relevant query arguments, and a set of options used to determine if the data actually should be re-fetched based on cache staleness.
180191

192+
The output is an object with methods:
193+
- `unwrap`: returns a promise that resolves to `undefined` when there are no pending requests initiated by this thunk.
194+
- `abort`: cancels pending requests.
195+
181196
React Hooks users will most likely never need to use this directly, as the `usePrefetch` hook will dispatch the thunk action creator result internally as needed when you call the prefetching function supplied by the hook.
182197

183198
#### Example

docs/rtk-query/api/created-api/hooks.mdx

+12-2
Original file line numberDiff line numberDiff line change
@@ -586,24 +586,34 @@ const prefetchCallback = api.usePrefetch(endpointName, options)
586586
#### Signature
587587

588588
```ts no-transpile
589+
interface PrefetchActionCreatorResult {
590+
// resolves when there are no pending request.
591+
unwrap(): Promise<void>
592+
// cancels pending requests.
593+
abort(): void
594+
}
595+
589596
type UsePrefetch = (
590597
endpointName: string,
591598
options?: UsePrefetchOptions
592599
) => PrefetchCallback
593600

594601
type UsePrefetchOptions =
595602
| {
603+
// how long is seconds before the data retrived by this prefetch request is considered unused.
604+
keepSubscriptionFor?: number
596605
// If specified, only runs the query if the difference between `new Date()` and the last
597606
// `fulfilledTimeStamp` is greater than the given value (in seconds)
598607
ifOlderThan?: false | number
599608
}
600609
| {
610+
keepSubscriptionFor?: number
601611
// If `force: true`, it will ignore the `ifOlderThan` value if it is set and the query
602612
// will be run even if it exists in the cache.
603613
force?: boolean
604614
}
605615

606-
type PrefetchCallback = (arg: any, options?: UsePrefetchOptions) => void
616+
type PrefetchCallback = (arg: any, options?: UsePrefetchOptions) => PrefetchActionCreatorResult
607617
```
608618
609619
- **Parameters**
@@ -612,7 +622,7 @@ type PrefetchCallback = (arg: any, options?: UsePrefetchOptions) => void
612622
- `options`: A set of options that control whether the prefetch request should occur
613623
614624
- **Returns**
615-
- A `prefetch` callback that when called, will initiate fetching the data for the provided endpoint
625+
- A `prefetch` callback that when called, will initiate fetching the data for the provided endpoint and will return `PrefetchActionCreatorResult`.
616626
617627
#### Description
618628

docs/rtk-query/usage/prefetching.mdx

+11-4
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,23 @@ Similar to the [`useMutation`](./mutations) hook, the `usePrefetch` hook will no
2626
It accepts two arguments: the first is the key of a query action that you [defined in your API service](../api/createApi#endpoints), and the second is an object of two optional parameters:
2727

2828
```ts title="usePrefetch Signature" no-transpile
29+
export interface PrefetchActionCreatorResult {
30+
unwrap(): Promise<void>,
31+
abort(): void
32+
}
33+
2934
export type PrefetchOptions =
30-
| { force?: boolean }
3135
| {
32-
ifOlderThan?: false | number;
33-
};
36+
ifOlderThan?: false | number,
37+
keepSubscriptionFor?: number
38+
}
39+
| { force?: boolean, keepSubscriptionFor?: number }
40+
3441

3542
usePrefetch<EndpointName extends QueryKeys<Definitions>>(
3643
endpointName: EndpointName,
3744
options?: PrefetchOptions
38-
): (arg: QueryArgFrom<Definitions[EndpointName]>, options?: PrefetchOptions) => void;
45+
): (arg: QueryArgFrom<Definitions[EndpointName]>, options?: PrefetchOptions) => PrefetchActionCreatorResult;
3946
```
4047

4148
### Customizing the Hook Behavior

packages/toolkit/src/query/core/apiState.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ export type ConfigState<ReducerPath> = RefetchConfigOptions & {
253253
} & ModifiableConfigState
254254

255255
export type ModifiableConfigState = {
256-
keepUnusedDataFor: number,
256+
keepUnusedDataFor: number
257257
keepPrefetchSubscriptionsFor: number
258258
} & RefetchConfigOptions
259259

packages/toolkit/src/query/core/buildInitiate.ts

+22-4
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,14 @@ declare module './module' {
3434
}
3535

3636
export interface PrefetchSubscribriptionOptions {
37-
keepSubscriptionFor?: number;
37+
keepSubscriptionFor?: number
3838
}
3939

4040
export interface StartQueryActionCreatorOptions {
4141
subscribe?: boolean
4242
forceRefetch?: boolean | number
43-
subscriptionOptions?: SubscriptionOptions,
44-
prefetch?: boolean | PrefetchSubscribriptionOptions,
43+
subscriptionOptions?: SubscriptionOptions
44+
prefetch?: boolean | PrefetchSubscribriptionOptions
4545
}
4646

4747
type StartQueryActionCreator<
@@ -65,6 +65,21 @@ export type QueryActionCreatorResult<
6565
queryCacheKey: string
6666
}
6767

68+
/**
69+
* @public
70+
*/
71+
export interface PrefetchActionCreatorResult {
72+
/**
73+
* Returns a promise that **always** resolves to `undefined`
74+
* when there are no pending requests initiated by input thunk.
75+
*/
76+
unwrap(): Promise<void>
77+
/**
78+
* Cancels pending requests.
79+
*/
80+
abort(): void
81+
}
82+
6883
type StartMutationActionCreator<
6984
D extends MutationDefinition<any, any, any, any>
7085
> = (
@@ -263,7 +278,10 @@ Features like automatic cache collection, automatic refetching etc. will not be
263278
endpointDefinition: QueryDefinition<any, any, any, any>
264279
) {
265280
const queryAction: StartQueryActionCreator<any> =
266-
(arg, { subscribe = true, forceRefetch, subscriptionOptions, prefetch } = {}) =>
281+
(
282+
arg,
283+
{ subscribe = true, forceRefetch, subscriptionOptions, prefetch } = {}
284+
) =>
267285
(dispatch, getState) => {
268286
const queryCacheKey = serializeQueryArgs({
269287
queryArgs: arg,

packages/toolkit/src/query/core/buildMiddleware/cacheLifecycle.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { AnyAction } from 'redux'
33
import type { ThunkDispatch } from 'redux-thunk'
44
import type { BaseQueryFn, BaseQueryMeta } from '../../baseQueryTypes'
55
import { DefinitionType } from '../../endpointDefinitions'
6+
import { catchRejection } from '../../utils/promise'
67
import type { RootState } from '../apiState'
78
import type {
89
MutationResultSelectorResult,
@@ -292,7 +293,7 @@ export const build: SubMiddlewareBuilder = ({
292293
])
293294
// prevent uncaught promise rejections from happening.
294295
// if the original promise is used in any way, that will create a new promise that will throw again
295-
cacheDataLoaded.catch(() => {})
296+
catchRejection(cacheDataLoaded)
296297
lifecycleMap[queryCacheKey] = lifecycle
297298
const selector = (api.endpoints[endpointName] as any).select(
298299
endpointDefinition.type === DefinitionType.query

packages/toolkit/src/query/core/buildMiddleware/queryLifecycle.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type {
1212
PromiseWithKnownReason,
1313
PromiseConstructorWithKnownReason,
1414
} from './types'
15+
import { catchRejection } from '../../utils/promise'
1516

1617
export type ReferenceQueryLifecycle = never
1718

@@ -240,7 +241,7 @@ export const build: SubMiddlewareBuilder = ({
240241
})
241242
// prevent uncaught promise rejections from happening.
242243
// if the original promise is used in any way, that will create a new promise that will throw again
243-
queryFulfilled.catch(() => {})
244+
catchRejection(queryFulfilled)
244245
lifecycleMap[requestId] = lifecycle
245246
const selector = (api.endpoints[endpointName] as any).select(
246247
endpointDefinition.type === DefinitionType.query

packages/toolkit/src/query/core/buildThunks.ts

+38-15
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import type {
77
} from '../baseQueryTypes'
88
import type { RootState, QueryKeys, QuerySubstateIdentifier } from './apiState'
99
import { QueryStatus } from './apiState'
10-
import type { StartQueryActionCreatorOptions } from './buildInitiate'
10+
import type {
11+
PrefetchActionCreatorResult,
12+
StartQueryActionCreatorOptions,
13+
} from './buildInitiate'
1114
import type {
1215
AssertTagTypes,
1316
EndpointDefinition,
@@ -41,6 +44,7 @@ import { HandledError } from '../HandledError'
4144

4245
import type { ApiEndpointQuery, PrefetchOptions } from './module'
4346
import type { UnwrapPromise } from '../tsHelpers'
47+
import { noop } from '../utils/promise'
4448

4549
declare module './module' {
4650
export interface ApiEndpointQuery<
@@ -444,37 +448,56 @@ In the case of an unhandled error, no tags will be "provided" or "invalidated".`
444448
endpointName: EndpointName,
445449
arg: any,
446450
options: PrefetchOptions
447-
): ThunkAction<void, any, any, AnyAction> =>
448-
(dispatch: ThunkDispatch<any, any, any>, getState: () => any) => {
451+
): ThunkAction<PrefetchActionCreatorResult, any, any, AnyAction> =>
452+
(dispatch: ThunkDispatch<any, any, AnyAction>, getState: () => any) => {
449453
const force = hasTheForce(options) && options.force
450454
const maxAge = hasMaxAge(options) && options.ifOlderThan
451455

452-
const queryAction = (force: boolean = true) =>
453-
(api.endpoints[endpointName] as ApiEndpointQuery<any, any>).initiate(
454-
arg,
455-
{ forceRefetch: force, prefetch: options || true }
456+
const dispatchPrefetchRequest = (
457+
forceRefetch: boolean = true
458+
): PrefetchActionCreatorResult => {
459+
const initiateOutput = dispatch(
460+
(api.endpoints[endpointName] as ApiEndpointQuery<any, any>).initiate(
461+
arg,
462+
{ forceRefetch, prefetch: options || true }
463+
)
456464
)
457-
const latestStateValue = (
458-
api.endpoints[endpointName] as ApiEndpointQuery<any, any>
459-
).select(arg)(getState())
465+
466+
return {
467+
unwrap() {
468+
return initiateOutput.unwrap().then(noop, noop)
469+
},
470+
abort: initiateOutput.abort,
471+
}
472+
}
460473

461474
if (force) {
462-
dispatch(queryAction())
475+
return dispatchPrefetchRequest()
463476
} else if (maxAge) {
477+
const latestStateValue = (
478+
api.endpoints[endpointName] as ApiEndpointQuery<any, any>
479+
).select(arg)(getState())
480+
464481
const lastFulfilledTs = latestStateValue?.fulfilledTimeStamp
465482
if (!lastFulfilledTs) {
466-
dispatch(queryAction())
467-
return
483+
return dispatchPrefetchRequest()
468484
}
469485
const shouldRetrigger =
470486
(Number(new Date()) - Number(new Date(lastFulfilledTs))) / 1000 >=
471487
maxAge
472488
if (shouldRetrigger) {
473-
dispatch(queryAction())
489+
return dispatchPrefetchRequest()
474490
}
475491
} else {
476492
// If prefetching with no options, just let it try
477-
dispatch(queryAction(false))
493+
return dispatchPrefetchRequest(false)
494+
}
495+
496+
return {
497+
unwrap() {
498+
return Promise.resolve()
499+
},
500+
abort: noop,
478501
}
479502
}
480503

packages/toolkit/src/query/core/module.ts

+12-7
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { buildMiddleware } from './buildMiddleware'
3333
import { buildSelectors } from './buildSelectors'
3434
import type {
3535
MutationActionCreatorResult,
36+
PrefetchActionCreatorResult,
3637
QueryActionCreatorResult,
3738
} from './buildInitiate'
3839
import { buildInitiate } from './buildInitiate'
@@ -49,21 +50,21 @@ import { enablePatches } from 'immer'
4950
/**
5051
* `ifOlderThan` - (default: `false` | `number`) - _number is value in seconds_
5152
* - If specified, it will only run the query if the difference between `new Date()` and the last `fulfilledTimeStamp` is greater than the given value
52-
*
53+
*
5354
* - `keepSubscriptionFor`: how long before the data is considered unused;
5455
* defaults to `api.config.keepPrefetchSubscriptionsFor`. - _number is value in seconds_
55-
*
56-
*
56+
*
57+
*
5758
* @overloadSummary
5859
* `force`
5960
* - If `force: true`, it will ignore the `ifOlderThan` value if it is set and the query will be run even if it exists in the cache.
6061
*/
6162
export type PrefetchOptions =
6263
| {
63-
ifOlderThan?: false | number,
64-
keepSubscriptionFor?: number,
64+
ifOlderThan?: false | number
65+
keepSubscriptionFor?: number
6566
}
66-
| { force?: boolean, keepSubscriptionFor?: number, }
67+
| { force?: boolean; keepSubscriptionFor?: number }
6768

6869
export const coreModuleName = /* @__PURE__ */ Symbol()
6970
export type CoreModule =
@@ -174,6 +175,10 @@ declare module '../apiTypes' {
174175
*
175176
* The thunk accepts three arguments: the name of the endpoint we are updating (such as `'getPost'`), any relevant query arguments, and a set of options used to determine if the data actually should be re-fetched based on cache staleness.
176177
*
178+
* The output is an object with the following methods:
179+
* - `unwrap`: returns a promise that resolves to `undefined` when there are no pending requests initiated by this thunk.
180+
* - `abort`: cancels pending requests.
181+
*
177182
* React Hooks users will most likely never need to use this directly, as the `usePrefetch` hook will dispatch this thunk internally as needed when you call the prefetching function supplied by the hook.
178183
*
179184
* @example
@@ -186,7 +191,7 @@ declare module '../apiTypes' {
186191
endpointName: EndpointName,
187192
arg: QueryArgFrom<Definitions[EndpointName]>,
188193
options: PrefetchOptions
189-
): ThunkAction<void, any, any, AnyAction>
194+
): ThunkAction<PrefetchActionCreatorResult, any, any, AnyAction>
190195
/**
191196
* A Redux thunk action creator that, when dispatched, creates and applies a set of JSON diff/patch objects to the current state. This immediately updates the Redux state with those changes.
192197
*

0 commit comments

Comments
 (0)