Skip to content

Commit e126f17

Browse files
authored
Merge pull request #2481 from reduxjs/fix-2477
2 parents 76cedeb + a73aee9 commit e126f17

File tree

7 files changed

+300
-83
lines changed

7 files changed

+300
-83
lines changed

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

+27-21
Original file line numberDiff line numberDiff line change
@@ -249,53 +249,59 @@ Note that [hooks](./hooks.mdx) also track state in local component state and mig
249249
dispatch(api.util.resetApiState())
250250
```
251251

252-
## `getRunningOperationPromises`
252+
## `getRunningQueriesThunk` and `getRunningMutationsThunk`
253253

254254
#### Signature
255255

256256
```ts no-transpile
257-
getRunningOperationPromises: () => Array<Promise<unknown>>
257+
getRunningQueriesThunk(): ThunkWithReturnValue<Array<QueryActionCreatorResult<any>>>
258+
getRunningMutationsThunk(): ThunkWithReturnValue<Array<MutationActionCreatorResult<any>>>
258259
```
259260

260261
#### Description
261262

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

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

267-
```ts no-transpile title="Awaiting all currently running queries & mutations example"
268-
await Promise.all(api.util.getRunningOperationPromises())
269+
```ts no-transpile title="Awaiting all currently running queries example"
270+
await Promise.all(dispatch(api.util.getRunningQueriesThunk()))
269271
```
270272

271-
## `getRunningOperationPromise`
273+
## `getRunningQueryThunk` and `getRunningMutationThunk`
272274

273275
#### Signature
274276

275277
```ts no-transpile
276-
getRunningOperationPromise: <EndpointName extends QueryKeys<Definitions>>(
278+
getRunningQueryThunk<EndpointName extends QueryKeys<Definitions>>(
277279
endpointName: EndpointName,
278280
args: QueryArgFrom<Definitions[EndpointName]>
279-
) =>
280-
| QueryActionCreatorResult<Definitions[EndpointName]>
281+
): ThunkWithReturnValue<
282+
| QueryActionCreatorResult<
283+
Definitions[EndpointName] & { type: 'query' }
284+
>
281285
| undefined
286+
>
282287

283-
getRunningOperationPromise: <EndpointName extends MutationKeys<Definitions>>(
288+
getRunningMutationThunk<EndpointName extends MutationKeys<Definitions>>(
284289
endpointName: EndpointName,
285290
fixedCacheKeyOrRequestId: string
286-
) =>
287-
| MutationActionCreatorResult<Definitions[EndpointName]>
291+
): ThunkWithReturnValue<
292+
| MutationActionCreatorResult<
293+
Definitions[EndpointName] & { type: 'mutation' }
294+
>
288295
| undefined
296+
>
289297
```
290298

291299
#### Description
292300

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

296-
When used with mutation endpoints, it accepts a [fixed cache key](./hooks.mdx#signature-1)
297-
or request ID rather than the argument.
298-
299-
This is primarily added to add experimental support for suspense in the future.
300-
It enables writing custom hooks that look up if RTK Query has already got a running promise
301-
for a certain endpoint/argument combination, and retrieving that promise to `throw` it.
305+
These thunks are primarily added to add experimental support for suspense in the future.
306+
They enable writing custom hooks that look up if RTK Query has already got a running query/mutation
307+
for a certain endpoint/argument combination, and retrieving that to `throw` it as a promise.

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

+20-11
Original file line numberDiff line numberDiff line change
@@ -53,20 +53,29 @@ type Api = {
5353
Array<TagTypes | FullTagDescription<TagTypes>>,
5454
string
5555
>
56-
resetApiState: SliceActions['resetApiState']
57-
getRunningOperationPromises: () => Array<Promise<unknown>>
58-
getRunningOperationPromise: <EndpointName extends QueryKeys<Definitions>>(
56+
selectInvalidatedBy: (
57+
state: FullState,
58+
tags: Array<TagTypes | FullTagDescription<TagTypes>>
59+
) => Array<{
60+
endpointName: string
61+
originalArgs: any
62+
queryCacheKey: string
63+
}>
64+
resetApiState: ActionCreator<ResetAction>
65+
getRunningQueryThunk(
5966
endpointName: EndpointName,
60-
args: QueryArgFrom<Definitions[EndpointName]>
61-
) =>
62-
| QueryActionCreatorResult<Definitions[EndpointName]>
63-
| undefined
64-
getRunningOperationPromise: <EndpointName extends MutationKeys<Definitions>>(
67+
args: QueryArg
68+
): ThunkWithReturnValue<QueryActionCreatorResult | undefined>
69+
getRunningMutationThunk(
6570
endpointName: EndpointName,
6671
fixedCacheKeyOrRequestId: string
67-
) =>
68-
| MutationActionCreatorResult<Definitions[EndpointName]>
69-
| undefined
72+
): ThunkWithReturnValue<MutationActionCreatorResult | undefined>
73+
getRunningQueriesThunk(): ThunkWithReturnValue<
74+
Array<QueryActionCreatorResult<any>>
75+
>
76+
getRunningMutationsThunk(): ThunkWithReturnValue<
77+
Array<MutationActionCreatorResult<any>>
78+
>
7079
}
7180

7281
// Internal actions

docs/rtk-query/usage/server-side-rendering.mdx

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ The workflow is as follows:
2121
- Set up `next-redux-wrapper`
2222
- In `getStaticProps` or `getServerSideProps`:
2323
- Pre-fetch all queries via the `initiate` actions, e.g. `store.dispatch(api.endpoints.getPokemonByName.initiate(name))`
24-
- Wait for each query to finish using `await Promise.all(api.util.getRunningOperationPromises())`
24+
- Wait for each query to finish using `await Promise.all(dispatch(api.util.getRunningQueriesThunk()))`
2525
- In your `createApi` call, configure rehydration using the `extractRehydrationInfo` option:
2626

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

5858
- Use your custom `createApi` when calling `const api = createApi({...})`
59-
- Wait for all queries to finish using `await Promise.all(api.util.getRunningOperationPromises())` before performing the next render cycle
59+
- Wait for all queries to finish using `await Promise.all(dispatch(api.util.getRunningQueriesThunk()))` before performing the next render cycle

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

+97-32
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import type { Api, ApiContext } from '../apiTypes'
1414
import type { ApiEndpointQuery } from './module'
1515
import type { BaseQueryError, QueryReturnValue } from '../baseQueryTypes'
1616
import type { QueryResultSelectorResult } from './buildSelectors'
17+
import type { Dispatch } from 'redux'
18+
import { isNotNullish } from '../utils/isNotNullish'
1719

1820
declare module './module' {
1921
export interface ApiEndpointQuery<
@@ -196,14 +198,14 @@ export function buildInitiate({
196198
api: Api<any, EndpointDefinitions, any, any>
197199
context: ApiContext<EndpointDefinitions>
198200
}) {
199-
const runningQueries: Record<
200-
string,
201-
QueryActionCreatorResult<any> | undefined
202-
> = {}
203-
const runningMutations: Record<
204-
string,
205-
MutationActionCreatorResult<any> | undefined
206-
> = {}
201+
const runningQueries: Map<
202+
Dispatch,
203+
Record<string, QueryActionCreatorResult<any> | undefined>
204+
> = new Map()
205+
const runningMutations: Map<
206+
Dispatch,
207+
Record<string, MutationActionCreatorResult<any> | undefined>
208+
> = new Map()
207209

208210
const {
209211
unsubscribeQueryResult,
@@ -213,32 +215,80 @@ export function buildInitiate({
213215
return {
214216
buildInitiateQuery,
215217
buildInitiateMutation,
218+
getRunningQueryThunk,
219+
getRunningMutationThunk,
220+
getRunningQueriesThunk,
221+
getRunningMutationsThunk,
216222
getRunningOperationPromises,
217-
getRunningOperationPromise,
223+
removalWarning,
218224
}
219225

220-
function getRunningOperationPromise(
221-
endpointName: string,
222-
argOrRequestId: any
223-
): any {
224-
const endpointDefinition = context.endpointDefinitions[endpointName]
225-
if (endpointDefinition.type === DefinitionType.query) {
226+
/** @deprecated to be removed in 2.0 */
227+
function removalWarning(): never {
228+
throw new Error(
229+
`This method had to be removed due to a conceptual bug in RTK.
230+
Please see https://github.com/reduxjs/redux-toolkit/pull/2481 for details.
231+
See https://redux-toolkit.js.org/rtk-query/usage/server-side-rendering for new guidance on SSR.`
232+
)
233+
}
234+
235+
/** @deprecated to be removed in 2.0 */
236+
function getRunningOperationPromises() {
237+
if (
238+
typeof process !== 'undefined' &&
239+
process.env.NODE_ENV === 'development'
240+
) {
241+
removalWarning()
242+
} else {
243+
const extract = <T>(
244+
v: Map<Dispatch<AnyAction>, Record<string, T | undefined>>
245+
) =>
246+
Array.from(v.values()).flatMap((queriesForStore) =>
247+
queriesForStore ? Object.values(queriesForStore) : []
248+
)
249+
return [...extract(runningQueries), ...extract(runningMutations)].filter(
250+
isNotNullish
251+
)
252+
}
253+
}
254+
255+
function getRunningQueryThunk(endpointName: string, queryArgs: any) {
256+
return (dispatch: Dispatch) => {
257+
const endpointDefinition = context.endpointDefinitions[endpointName]
226258
const queryCacheKey = serializeQueryArgs({
227-
queryArgs: argOrRequestId,
259+
queryArgs,
228260
endpointDefinition,
229261
endpointName,
230262
})
231-
return runningQueries[queryCacheKey]
232-
} else {
233-
return runningMutations[argOrRequestId]
263+
return runningQueries.get(dispatch)?.[queryCacheKey] as
264+
| QueryActionCreatorResult<never>
265+
| undefined
234266
}
235267
}
236268

237-
function getRunningOperationPromises() {
238-
return [
239-
...Object.values(runningQueries),
240-
...Object.values(runningMutations),
241-
].filter(<T>(t: T | undefined): t is T => !!t)
269+
function getRunningMutationThunk(
270+
/**
271+
* this is only here to allow TS to infer the result type by input value
272+
* we could use it to validate the result, but it's probably not necessary
273+
*/
274+
_endpointName: string,
275+
fixedCacheKeyOrRequestId: string
276+
) {
277+
return (dispatch: Dispatch) => {
278+
return runningMutations.get(dispatch)?.[fixedCacheKeyOrRequestId] as
279+
| MutationActionCreatorResult<never>
280+
| undefined
281+
}
282+
}
283+
284+
function getRunningQueriesThunk() {
285+
return (dispatch: Dispatch) =>
286+
Object.values(runningQueries.get(dispatch) || {}).filter(isNotNullish)
287+
}
288+
289+
function getRunningMutationsThunk() {
290+
return (dispatch: Dispatch) =>
291+
Object.values(runningMutations.get(dispatch) || {}).filter(isNotNullish)
242292
}
243293

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

303353
const skippedSynchronously = stateAfter.requestId !== requestId
304354

305-
const runningQuery = runningQueries[queryCacheKey]
355+
const runningQuery = runningQueries.get(dispatch)?.[queryCacheKey]
306356
const selectFromState = () => selector(getState())
307357

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

362412
if (!runningQuery && !skippedSynchronously && !forceQueryFn) {
363-
runningQueries[queryCacheKey] = statePromise
413+
const running = runningQueries.get(dispatch) || {}
414+
running[queryCacheKey] = statePromise
415+
runningQueries.set(dispatch, running)
416+
364417
statePromise.then(() => {
365-
delete runningQueries[queryCacheKey]
418+
delete running[queryCacheKey]
419+
if (!Object.keys(running).length) {
420+
runningQueries.delete(dispatch)
421+
}
366422
})
367423
}
368424

@@ -404,15 +460,24 @@ Features like automatic cache collection, automatic refetching etc. will not be
404460
reset,
405461
})
406462

407-
runningMutations[requestId] = ret
463+
const running = runningMutations.get(dispatch) || {}
464+
runningMutations.set(dispatch, running)
465+
running[requestId] = ret
408466
ret.then(() => {
409-
delete runningMutations[requestId]
467+
delete running[requestId]
468+
if (!Object.keys(running).length) {
469+
runningMutations.delete(dispatch)
470+
}
410471
})
411472
if (fixedCacheKey) {
412-
runningMutations[fixedCacheKey] = ret
473+
running[fixedCacheKey] = ret
413474
ret.then(() => {
414-
if (runningMutations[fixedCacheKey] === ret)
415-
delete runningMutations[fixedCacheKey]
475+
if (running[fixedCacheKey] === ret) {
476+
delete running[fixedCacheKey]
477+
if (!Object.keys(running).length) {
478+
runningMutations.delete(dispatch)
479+
}
480+
}
416481
})
417482
}
418483

0 commit comments

Comments
 (0)