Skip to content

Commit

Permalink
feat(rtk-query): prefetch thunk return type reduxjs#1283
Browse files Browse the repository at this point in the history
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
}
```
  • Loading branch information
FaberVitale committed Jun 11, 2022
1 parent 29b7673 commit 1f17ca7
Show file tree
Hide file tree
Showing 17 changed files with 275 additions and 88 deletions.
21 changes: 18 additions & 3 deletions docs/rtk-query/api/created-api/api-slice-utils.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -155,13 +155,23 @@ patchCollection.undo()
#### Signature

```ts no-transpile
type PrefetchOptions = { ifOlderThan?: false | number } | { force?: boolean };
interface PrefetchActionCreatorResult {
unwrap(): Promise<void>,
abort(): void
}

type PrefetchOptions =
| {
ifOlderThan?: false | number,
keepSubscriptionFor?: number
}
| { force?: boolean, keepSubscriptionFor?: number }

const prefetch = (
endpointName: string,
arg: any,
options: PrefetchOptions
) => ThunkAction<void, any, any, AnyAction>;
) => ThunkAction<PrefetchActionCreatorResult, any, any, AnyAction>
```

- **Parameters**
Expand All @@ -171,13 +181,18 @@ const prefetch = (
- `options`: options to determine whether the request should be sent for a given situation:
- `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)
- `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.

- `keepSubscriptionFor`: how long in seconds before the data retrieved is considered unused;
defaults to `api.config.keepPrefetchSubscriptionsFor`.
#### Description

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

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.

The output is an object with methods:
- `unwrap`: returns a promise that resolves to `undefined` when there are no pending requests initiated by this thunk.
- `abort`: cancels pending requests.

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.

#### Example
Expand Down
14 changes: 12 additions & 2 deletions docs/rtk-query/api/created-api/hooks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -586,24 +586,34 @@ const prefetchCallback = api.usePrefetch(endpointName, options)
#### Signature

```ts no-transpile
interface PrefetchActionCreatorResult {
// resolves when there are no pending request.
unwrap(): Promise<void>
// cancels pending requests.
abort(): void
}

type UsePrefetch = (
endpointName: string,
options?: UsePrefetchOptions
) => PrefetchCallback

type UsePrefetchOptions =
| {
// how long is seconds before the data retrived by this prefetch request is considered unused.
keepSubscriptionFor?: number
// If specified, only runs the query if the difference between `new Date()` and the last
// `fulfilledTimeStamp` is greater than the given value (in seconds)
ifOlderThan?: false | number
}
| {
keepSubscriptionFor?: number
// 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.
force?: boolean
}

type PrefetchCallback = (arg: any, options?: UsePrefetchOptions) => void
type PrefetchCallback = (arg: any, options?: UsePrefetchOptions) => PrefetchActionCreatorResult
```
- **Parameters**
Expand All @@ -612,7 +622,7 @@ type PrefetchCallback = (arg: any, options?: UsePrefetchOptions) => void
- `options`: A set of options that control whether the prefetch request should occur
- **Returns**
- A `prefetch` callback that when called, will initiate fetching the data for the provided endpoint
- A `prefetch` callback that when called, will initiate fetching the data for the provided endpoint and will return `PrefetchActionCreatorResult`.
#### Description
Expand Down
15 changes: 11 additions & 4 deletions docs/rtk-query/usage/prefetching.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,23 @@ Similar to the [`useMutation`](./mutations) hook, the `usePrefetch` hook will no
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:

```ts title="usePrefetch Signature" no-transpile
export interface PrefetchActionCreatorResult {
unwrap(): Promise<void>,
abort(): void
}

export type PrefetchOptions =
| { force?: boolean }
| {
ifOlderThan?: false | number;
};
ifOlderThan?: false | number,
keepSubscriptionFor?: number
}
| { force?: boolean, keepSubscriptionFor?: number }


usePrefetch<EndpointName extends QueryKeys<Definitions>>(
endpointName: EndpointName,
options?: PrefetchOptions
): (arg: QueryArgFrom<Definitions[EndpointName]>, options?: PrefetchOptions) => void;
): (arg: QueryArgFrom<Definitions[EndpointName]>, options?: PrefetchOptions) => PrefetchActionCreatorResult;
```

### Customizing the Hook Behavior
Expand Down
2 changes: 1 addition & 1 deletion packages/toolkit/src/query/core/apiState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ export type ConfigState<ReducerPath> = RefetchConfigOptions & {
} & ModifiableConfigState

export type ModifiableConfigState = {
keepUnusedDataFor: number,
keepUnusedDataFor: number
keepPrefetchSubscriptionsFor: number
} & RefetchConfigOptions

Expand Down
26 changes: 22 additions & 4 deletions packages/toolkit/src/query/core/buildInitiate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ declare module './module' {
}

export interface PrefetchSubscribriptionOptions {
keepSubscriptionFor?: number;
keepSubscriptionFor?: number
}

export interface StartQueryActionCreatorOptions {
subscribe?: boolean
forceRefetch?: boolean | number
subscriptionOptions?: SubscriptionOptions,
prefetch?: boolean | PrefetchSubscribriptionOptions,
subscriptionOptions?: SubscriptionOptions
prefetch?: boolean | PrefetchSubscribriptionOptions
}

type StartQueryActionCreator<
Expand All @@ -65,6 +65,21 @@ export type QueryActionCreatorResult<
queryCacheKey: string
}

/**
* @public
*/
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
}

type StartMutationActionCreator<
D extends MutationDefinition<any, any, any, any>
> = (
Expand Down Expand Up @@ -263,7 +278,10 @@ Features like automatic cache collection, automatic refetching etc. will not be
endpointDefinition: QueryDefinition<any, any, any, any>
) {
const queryAction: StartQueryActionCreator<any> =
(arg, { subscribe = true, forceRefetch, subscriptionOptions, prefetch } = {}) =>
(
arg,
{ subscribe = true, forceRefetch, subscriptionOptions, prefetch } = {}
) =>
(dispatch, getState) => {
const queryCacheKey = serializeQueryArgs({
queryArgs: arg,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { AnyAction } from 'redux'
import type { ThunkDispatch } from 'redux-thunk'
import type { BaseQueryFn, BaseQueryMeta } from '../../baseQueryTypes'
import { DefinitionType } from '../../endpointDefinitions'
import { catchRejection } from '../../utils/promise'
import type { RootState } from '../apiState'
import type {
MutationResultSelectorResult,
Expand Down Expand Up @@ -292,7 +293,7 @@ export const build: SubMiddlewareBuilder = ({
])
// prevent uncaught promise rejections from happening.
// if the original promise is used in any way, that will create a new promise that will throw again
cacheDataLoaded.catch(() => {})
catchRejection(cacheDataLoaded)
lifecycleMap[queryCacheKey] = lifecycle
const selector = (api.endpoints[endpointName] as any).select(
endpointDefinition.type === DefinitionType.query
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type {
PromiseWithKnownReason,
PromiseConstructorWithKnownReason,
} from './types'
import { catchRejection } from '../../utils/promise'

export type ReferenceQueryLifecycle = never

Expand Down Expand Up @@ -240,7 +241,7 @@ export const build: SubMiddlewareBuilder = ({
})
// prevent uncaught promise rejections from happening.
// if the original promise is used in any way, that will create a new promise that will throw again
queryFulfilled.catch(() => {})
catchRejection(queryFulfilled)
lifecycleMap[requestId] = lifecycle
const selector = (api.endpoints[endpointName] as any).select(
endpointDefinition.type === DefinitionType.query
Expand Down
53 changes: 38 additions & 15 deletions packages/toolkit/src/query/core/buildThunks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import type {
} from '../baseQueryTypes'
import type { RootState, QueryKeys, QuerySubstateIdentifier } from './apiState'
import { QueryStatus } from './apiState'
import type { StartQueryActionCreatorOptions } from './buildInitiate'
import type {
PrefetchActionCreatorResult,
StartQueryActionCreatorOptions,
} from './buildInitiate'
import type {
AssertTagTypes,
EndpointDefinition,
Expand Down Expand Up @@ -41,6 +44,7 @@ import { HandledError } from '../HandledError'

import type { ApiEndpointQuery, PrefetchOptions } from './module'
import type { UnwrapPromise } from '../tsHelpers'
import { noop } from '../utils/promise'

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

const queryAction = (force: boolean = true) =>
(api.endpoints[endpointName] as ApiEndpointQuery<any, any>).initiate(
arg,
{ forceRefetch: force, prefetch: options || true }
const dispatchPrefetchRequest = (
forceRefetch: boolean = true
): PrefetchActionCreatorResult => {
const initiateOutput = dispatch(
(api.endpoints[endpointName] as ApiEndpointQuery<any, any>).initiate(
arg,
{ forceRefetch, prefetch: options || true }
)
)
const latestStateValue = (
api.endpoints[endpointName] as ApiEndpointQuery<any, any>
).select(arg)(getState())

return {
unwrap() {
return initiateOutput.unwrap().then(noop, noop)
},
abort: initiateOutput.abort,
}
}

if (force) {
dispatch(queryAction())
return dispatchPrefetchRequest()
} else if (maxAge) {
const latestStateValue = (
api.endpoints[endpointName] as ApiEndpointQuery<any, any>
).select(arg)(getState())

const lastFulfilledTs = latestStateValue?.fulfilledTimeStamp
if (!lastFulfilledTs) {
dispatch(queryAction())
return
return dispatchPrefetchRequest()
}
const shouldRetrigger =
(Number(new Date()) - Number(new Date(lastFulfilledTs))) / 1000 >=
maxAge
if (shouldRetrigger) {
dispatch(queryAction())
return dispatchPrefetchRequest()
}
} else {
// If prefetching with no options, just let it try
dispatch(queryAction(false))
return dispatchPrefetchRequest(false)
}

return {
unwrap() {
return Promise.resolve()
},
abort: noop,
}
}

Expand Down
19 changes: 12 additions & 7 deletions packages/toolkit/src/query/core/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { buildMiddleware } from './buildMiddleware'
import { buildSelectors } from './buildSelectors'
import type {
MutationActionCreatorResult,
PrefetchActionCreatorResult,
QueryActionCreatorResult,
} from './buildInitiate'
import { buildInitiate } from './buildInitiate'
Expand All @@ -49,21 +50,21 @@ import { enablePatches } from 'immer'
/**
* `ifOlderThan` - (default: `false` | `number`) - _number is value in seconds_
* - If specified, it will only run the query if the difference between `new Date()` and the last `fulfilledTimeStamp` is greater than the given value
*
*
* - `keepSubscriptionFor`: how long before the data is considered unused;
* defaults to `api.config.keepPrefetchSubscriptionsFor`. - _number is value in seconds_
*
*
*
*
* @overloadSummary
* `force`
* - 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.
*/
export type PrefetchOptions =
| {
ifOlderThan?: false | number,
keepSubscriptionFor?: number,
ifOlderThan?: false | number
keepSubscriptionFor?: number
}
| { force?: boolean, keepSubscriptionFor?: number, }
| { force?: boolean; keepSubscriptionFor?: number }

export const coreModuleName = /* @__PURE__ */ Symbol()
export type CoreModule =
Expand Down Expand Up @@ -174,6 +175,10 @@ declare module '../apiTypes' {
*
* 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.
*
* The output is an object with the following methods:
* - `unwrap`: returns a promise that resolves to `undefined` when there are no pending requests initiated by this thunk.
* - `abort`: cancels pending requests.
*
* 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.
*
* @example
Expand All @@ -186,7 +191,7 @@ declare module '../apiTypes' {
endpointName: EndpointName,
arg: QueryArgFrom<Definitions[EndpointName]>,
options: PrefetchOptions
): ThunkAction<void, any, any, AnyAction>
): ThunkAction<PrefetchActionCreatorResult, any, any, AnyAction>
/**
* 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.
*
Expand Down
Loading

0 comments on commit 1f17ca7

Please sign in to comment.