Skip to content

[breaking fix] replace getRunningOperationPromise(s) with getRunning(Query|Queries|Mutation|Mutations)Thunk #2481

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 27 additions & 21 deletions docs/rtk-query/api/created-api/api-slice-utils.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -249,53 +249,59 @@ Note that [hooks](./hooks.mdx) also track state in local component state and mig
dispatch(api.util.resetApiState())
```

## `getRunningOperationPromises`
## `getRunningQueriesThunk` and `getRunningMutationsThunk`

#### Signature

```ts no-transpile
getRunningOperationPromises: () => Array<Promise<unknown>>
getRunningQueriesThunk(): ThunkWithReturnValue<Array<QueryActionCreatorResult<any>>>
getRunningMutationsThunk(): ThunkWithReturnValue<Array<MutationActionCreatorResult<any>>>
```

#### Description

A function that returns all promises for running queries and mutations.
Thunks that (if dispatched) return either all running queries or mutations.
These returned values can be awaited like promises.

This is useful for SSR scenarios to await everything triggered in any way, including via hook calls,
This is useful for SSR scenarios to await all queries (or mutations) triggered in any way, including via hook calls
or manually dispatching `initiate` actions.

```ts no-transpile title="Awaiting all currently running queries & mutations example"
await Promise.all(api.util.getRunningOperationPromises())
```ts no-transpile title="Awaiting all currently running queries example"
await Promise.all(dispatch(api.util.getRunningQueriesThunk()))
```

## `getRunningOperationPromise`
## `getRunningQueryThunk` and `getRunningMutationThunk`

#### Signature

```ts no-transpile
getRunningOperationPromise: <EndpointName extends QueryKeys<Definitions>>(
getRunningQueryThunk<EndpointName extends QueryKeys<Definitions>>(
endpointName: EndpointName,
args: QueryArgFrom<Definitions[EndpointName]>
) =>
| QueryActionCreatorResult<Definitions[EndpointName]>
): ThunkWithReturnValue<
| QueryActionCreatorResult<
Definitions[EndpointName] & { type: 'query' }
>
| undefined
>

getRunningOperationPromise: <EndpointName extends MutationKeys<Definitions>>(
getRunningMutationThunk<EndpointName extends MutationKeys<Definitions>>(
endpointName: EndpointName,
fixedCacheKeyOrRequestId: string
) =>
| MutationActionCreatorResult<Definitions[EndpointName]>
): ThunkWithReturnValue<
| MutationActionCreatorResult<
Definitions[EndpointName] & { type: 'mutation' }
>
| undefined
>
```

#### Description

A function that returns a single promise for a given endpoint name + argument combination,
if it is currently running. If it is not currently running, the function returns `undefined`.
Thunks that (if dispatched) return a single running query (or mutation) for a given
endpoint name + argument (or requestId/fixedCacheKey) combination, if it is currently running.
If it is not currently running, the function returns `undefined`.

When used with mutation endpoints, it accepts a [fixed cache key](./hooks.mdx#signature-1)
or request ID rather than the argument.

This is primarily added to add experimental support for suspense in the future.
It enables writing custom hooks that look up if RTK Query has already got a running promise
for a certain endpoint/argument combination, and retrieving that promise to `throw` it.
These thunks are primarily added to add experimental support for suspense in the future.
They enable writing custom hooks that look up if RTK Query has already got a running query/mutation
for a certain endpoint/argument combination, and retrieving that to `throw` it as a promise.
31 changes: 20 additions & 11 deletions docs/rtk-query/api/created-api/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -53,20 +53,29 @@ type Api = {
Array<TagTypes | FullTagDescription<TagTypes>>,
string
>
resetApiState: SliceActions['resetApiState']
getRunningOperationPromises: () => Array<Promise<unknown>>
getRunningOperationPromise: <EndpointName extends QueryKeys<Definitions>>(
selectInvalidatedBy: (
state: FullState,
tags: Array<TagTypes | FullTagDescription<TagTypes>>
) => Array<{
endpointName: string
originalArgs: any
queryCacheKey: string
}>
resetApiState: ActionCreator<ResetAction>
getRunningQueryThunk(
endpointName: EndpointName,
args: QueryArgFrom<Definitions[EndpointName]>
) =>
| QueryActionCreatorResult<Definitions[EndpointName]>
| undefined
getRunningOperationPromise: <EndpointName extends MutationKeys<Definitions>>(
args: QueryArg
): ThunkWithReturnValue<QueryActionCreatorResult | undefined>
getRunningMutationThunk(
endpointName: EndpointName,
fixedCacheKeyOrRequestId: string
) =>
| MutationActionCreatorResult<Definitions[EndpointName]>
| undefined
): ThunkWithReturnValue<MutationActionCreatorResult | undefined>
getRunningQueriesThunk(): ThunkWithReturnValue<
Array<QueryActionCreatorResult<any>>
>
getRunningMutationsThunk(): ThunkWithReturnValue<
Array<MutationActionCreatorResult<any>>
>
}

// Internal actions
Expand Down
4 changes: 2 additions & 2 deletions docs/rtk-query/usage/server-side-rendering.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ The workflow is as follows:
- Set up `next-redux-wrapper`
- In `getStaticProps` or `getServerSideProps`:
- Pre-fetch all queries via the `initiate` actions, e.g. `store.dispatch(api.endpoints.getPokemonByName.initiate(name))`
- Wait for each query to finish using `await Promise.all(api.util.getRunningOperationPromises())`
- Wait for each query to finish using `await Promise.all(dispatch(api.util.getRunningQueriesThunk()))`
- In your `createApi` call, configure rehydration using the `extractRehydrationInfo` option:

[examples](docblock://query/createApi.ts?token=CreateApiOptions.extractRehydrationInfo)
Expand Down Expand Up @@ -56,4 +56,4 @@ The workflow is as follows:
[examples](docblock://query/react/module.ts?token=ReactHooksModuleOptions.unstable__sideEffectsInRender)

- Use your custom `createApi` when calling `const api = createApi({...})`
- Wait for all queries to finish using `await Promise.all(api.util.getRunningOperationPromises())` before performing the next render cycle
- Wait for all queries to finish using `await Promise.all(dispatch(api.util.getRunningQueriesThunk()))` before performing the next render cycle
129 changes: 97 additions & 32 deletions packages/toolkit/src/query/core/buildInitiate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import type { Api, ApiContext } from '../apiTypes'
import type { ApiEndpointQuery } from './module'
import type { BaseQueryError, QueryReturnValue } from '../baseQueryTypes'
import type { QueryResultSelectorResult } from './buildSelectors'
import type { Dispatch } from 'redux'
import { isNotNullish } from '../utils/isNotNullish'

declare module './module' {
export interface ApiEndpointQuery<
Expand Down Expand Up @@ -196,14 +198,14 @@ export function buildInitiate({
api: Api<any, EndpointDefinitions, any, any>
context: ApiContext<EndpointDefinitions>
}) {
const runningQueries: Record<
string,
QueryActionCreatorResult<any> | undefined
> = {}
const runningMutations: Record<
string,
MutationActionCreatorResult<any> | undefined
> = {}
const runningQueries: Map<
Dispatch,
Record<string, QueryActionCreatorResult<any> | undefined>
> = new Map()
const runningMutations: Map<
Dispatch,
Record<string, MutationActionCreatorResult<any> | undefined>
> = new Map()

const {
unsubscribeQueryResult,
Expand All @@ -213,32 +215,80 @@ export function buildInitiate({
return {
buildInitiateQuery,
buildInitiateMutation,
getRunningQueryThunk,
getRunningMutationThunk,
getRunningQueriesThunk,
getRunningMutationsThunk,
getRunningOperationPromises,
getRunningOperationPromise,
removalWarning,
}

function getRunningOperationPromise(
endpointName: string,
argOrRequestId: any
): any {
const endpointDefinition = context.endpointDefinitions[endpointName]
if (endpointDefinition.type === DefinitionType.query) {
/** @deprecated to be removed in 2.0 */
function removalWarning(): never {
throw new Error(
`This method had to be removed due to a conceptual bug in RTK.
Please see https://github.com/reduxjs/redux-toolkit/pull/2481 for details.
See https://redux-toolkit.js.org/rtk-query/usage/server-side-rendering for new guidance on SSR.`
)
}

/** @deprecated to be removed in 2.0 */
function getRunningOperationPromises() {
if (
typeof process !== 'undefined' &&
process.env.NODE_ENV === 'development'
) {
removalWarning()
} else {
const extract = <T>(
v: Map<Dispatch<AnyAction>, Record<string, T | undefined>>
) =>
Array.from(v.values()).flatMap((queriesForStore) =>
queriesForStore ? Object.values(queriesForStore) : []
)
return [...extract(runningQueries), ...extract(runningMutations)].filter(
isNotNullish
)
}
}

function getRunningQueryThunk(endpointName: string, queryArgs: any) {
return (dispatch: Dispatch) => {
const endpointDefinition = context.endpointDefinitions[endpointName]
const queryCacheKey = serializeQueryArgs({
queryArgs: argOrRequestId,
queryArgs,
endpointDefinition,
endpointName,
})
return runningQueries[queryCacheKey]
} else {
return runningMutations[argOrRequestId]
return runningQueries.get(dispatch)?.[queryCacheKey] as
| QueryActionCreatorResult<never>
| undefined
}
}

function getRunningOperationPromises() {
return [
...Object.values(runningQueries),
...Object.values(runningMutations),
].filter(<T>(t: T | undefined): t is T => !!t)
function getRunningMutationThunk(
/**
* this is only here to allow TS to infer the result type by input value
* we could use it to validate the result, but it's probably not necessary
*/
_endpointName: string,
fixedCacheKeyOrRequestId: string
) {
return (dispatch: Dispatch) => {
return runningMutations.get(dispatch)?.[fixedCacheKeyOrRequestId] as
| MutationActionCreatorResult<never>
| undefined
}
}

function getRunningQueriesThunk() {
return (dispatch: Dispatch) =>
Object.values(runningQueries.get(dispatch) || {}).filter(isNotNullish)
}

function getRunningMutationsThunk() {
return (dispatch: Dispatch) =>
Object.values(runningMutations.get(dispatch) || {}).filter(isNotNullish)
}

function middlewareWarning(getState: () => RootState<{}, string, string>) {
Expand Down Expand Up @@ -302,7 +352,7 @@ Features like automatic cache collection, automatic refetching etc. will not be

const skippedSynchronously = stateAfter.requestId !== requestId

const runningQuery = runningQueries[queryCacheKey]
const runningQuery = runningQueries.get(dispatch)?.[queryCacheKey]
const selectFromState = () => selector(getState())

const statePromise: QueryActionCreatorResult<any> = Object.assign(
Expand Down Expand Up @@ -360,9 +410,15 @@ Features like automatic cache collection, automatic refetching etc. will not be
)

if (!runningQuery && !skippedSynchronously && !forceQueryFn) {
runningQueries[queryCacheKey] = statePromise
const running = runningQueries.get(dispatch) || {}
running[queryCacheKey] = statePromise
runningQueries.set(dispatch, running)

statePromise.then(() => {
delete runningQueries[queryCacheKey]
delete running[queryCacheKey]
if (!Object.keys(running).length) {
runningQueries.delete(dispatch)
}
})
}

Expand Down Expand Up @@ -404,15 +460,24 @@ Features like automatic cache collection, automatic refetching etc. will not be
reset,
})

runningMutations[requestId] = ret
const running = runningMutations.get(dispatch) || {}
runningMutations.set(dispatch, running)
running[requestId] = ret
ret.then(() => {
delete runningMutations[requestId]
delete running[requestId]
if (!Object.keys(running).length) {
runningMutations.delete(dispatch)
}
})
if (fixedCacheKey) {
runningMutations[fixedCacheKey] = ret
running[fixedCacheKey] = ret
ret.then(() => {
if (runningMutations[fixedCacheKey] === ret)
delete runningMutations[fixedCacheKey]
if (running[fixedCacheKey] === ret) {
delete running[fixedCacheKey]
if (!Object.keys(running).length) {
runningMutations.delete(dispatch)
}
}
})
}

Expand Down
Loading