Skip to content

Commit 767fede

Browse files
authored
Merge pull request #3255 from dutzi/pr/updateQueryData-provide
2 parents 9f4fdea + ca0b28e commit 767fede

File tree

5 files changed

+272
-76
lines changed

5 files changed

+272
-76
lines changed

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

+28-24
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ Some of the TS types on this page are pseudocode to illustrate intent, as the ac
2929
const updateQueryData = (
3030
endpointName: string,
3131
args: any,
32-
updateRecipe: (draft: Draft<CachedState>) => void
32+
updateRecipe: (draft: Draft<CachedState>) => void,
33+
updateProvided?: boolean
3334
) => ThunkAction<PatchCollection, PartialState, any, AnyAction>;
3435

3536
interface PatchCollection {
@@ -43,6 +44,7 @@ interface PatchCollection {
4344
- `endpointName`: a string matching an existing endpoint name
4445
- `args`: an argument matching that used for a previous query call, used to determine which cached dataset needs to be updated
4546
- `updateRecipe`: an Immer `produce` callback that can apply changes to the cached state
47+
- `updateProvided`: a boolean indicating whether the endpoint's provided tags should be re-calculated based on the updated cache. Defaults to `false`.
4648

4749
#### Description
4850

@@ -155,14 +157,16 @@ await dispatch(
155157
const patchQueryData = (
156158
endpointName: string,
157159
args: any
158-
patches: Patch[]
160+
patches: Patch[],
161+
updateProvided?: boolean
159162
) => ThunkAction<void, PartialState, any, AnyAction>;
160163
```
161164

162165
- **Parameters**
163166
- `endpointName`: a string matching an existing endpoint name
164167
- `args`: a cache key, used to determine which cached dataset needs to be updated
165168
- `patches`: an array of patches (or inverse patches) to apply to cached state. These would typically be obtained from the result of dispatching [`updateQueryData`](#updatequerydata)
169+
- `updateProvided`: a boolean indicating whether the endpoint's provided tags should be re-calculated based on the updated cache. Defaults to `false`.
166170

167171
#### Description
168172

@@ -229,42 +233,42 @@ dispatch(api.util.prefetch('getPosts', undefined, { force: true }))
229233
```
230234

231235
### `selectInvalidatedBy`
232-
236+
233237
#### Signature
234-
238+
235239
```ts no-transpile
236-
function selectInvalidatedBy(
237-
state: RootState,
238-
tags: ReadonlyArray<TagDescription<string>>
239-
): Array<{
240-
endpointName: string
241-
originalArgs: any
242-
queryCacheKey: QueryCacheKey
243-
}>
240+
function selectInvalidatedBy(
241+
state: RootState,
242+
tags: ReadonlyArray<TagDescription<string>>
243+
): Array<{
244+
endpointName: string
245+
originalArgs: any
246+
queryCacheKey: QueryCacheKey
247+
}>
244248
```
245-
249+
246250
- **Parameters**
247251
- `state`: the root state
248252
- `tags`: a readonly array of invalidated tags, where the provided `TagDescription` is one of the strings provided to the [`tagTypes`](../createApi.mdx#tagtypes) property of the api. e.g.
249253
- `[TagType]`
250254
- `[{ type: TagType }]`
251255
- `[{ type: TagType, id: number | string }]`
252-
256+
253257
#### Description
254-
258+
255259
A function that can select query parameters to be invalidated.
256-
260+
257261
The function accepts two arguments
258-
- the root state and
259-
- the cache tags to be invalidated.
260-
262+
- the root state and
263+
- the cache tags to be invalidated.
264+
261265
It returns an array that contains
262-
- the endpoint name,
263-
- the original args and
264-
- the queryCacheKey.
265-
266+
- the endpoint name,
267+
- the original args and
268+
- the queryCacheKey.
269+
266270
#### Example
267-
271+
268272
```ts no-transpile
269273
dispatch(api.util.selectInvalidatedBy(state, ['Post']))
270274
dispatch(api.util.selectInvalidatedBy(state, [{ type: 'Post', id: 1 }]))

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

+60-32
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { calculateProvidedByThunk } from './buildThunks'
2929
import type {
3030
AssertTagTypes,
3131
EndpointDefinitions,
32+
FullTagDescription,
3233
QueryDefinition,
3334
} from '../endpointDefinitions'
3435
import type { Patch } from 'immer'
@@ -125,17 +126,22 @@ export function buildSlice({
125126
},
126127
prepare: prepareAutoBatched<QuerySubstateIdentifier>(),
127128
},
128-
queryResultPatched(
129-
draft,
130-
{
131-
payload: { queryCacheKey, patches },
132-
}: PayloadAction<
129+
queryResultPatched: {
130+
reducer(
131+
draft,
132+
{
133+
payload: { queryCacheKey, patches },
134+
}: PayloadAction<
135+
QuerySubstateIdentifier & { patches: readonly Patch[] }
136+
>
137+
) {
138+
updateQuerySubstateIfExists(draft, queryCacheKey, (substate) => {
139+
substate.data = applyPatches(substate.data as any, patches.concat())
140+
})
141+
},
142+
prepare: prepareAutoBatched<
133143
QuerySubstateIdentifier & { patches: readonly Patch[] }
134-
>
135-
) {
136-
updateQuerySubstateIfExists(draft, queryCacheKey, (substate) => {
137-
substate.data = applyPatches(substate.data as any, patches.concat())
138-
})
144+
>(),
139145
},
140146
},
141147
extraReducers(builder) {
@@ -325,7 +331,42 @@ export function buildSlice({
325331
const invalidationSlice = createSlice({
326332
name: `${reducerPath}/invalidation`,
327333
initialState: initialState as InvalidationState<string>,
328-
reducers: {},
334+
reducers: {
335+
updateProvidedBy: {
336+
reducer(
337+
draft,
338+
action: PayloadAction<{
339+
queryCacheKey: QueryCacheKey
340+
providedTags: readonly FullTagDescription<string>[]
341+
}>
342+
) {
343+
const { queryCacheKey, providedTags } = action.payload
344+
345+
for (const tagTypeSubscriptions of Object.values(draft)) {
346+
for (const idSubscriptions of Object.values(tagTypeSubscriptions)) {
347+
const foundAt = idSubscriptions.indexOf(queryCacheKey)
348+
if (foundAt !== -1) {
349+
idSubscriptions.splice(foundAt, 1)
350+
}
351+
}
352+
}
353+
354+
for (const { type, id } of providedTags) {
355+
const subscribedQueries = ((draft[type] ??= {})[
356+
id || '__internal_without_id'
357+
] ??= [])
358+
const alreadySubscribed = subscribedQueries.includes(queryCacheKey)
359+
if (!alreadySubscribed) {
360+
subscribedQueries.push(queryCacheKey)
361+
}
362+
}
363+
},
364+
prepare: prepareAutoBatched<{
365+
queryCacheKey: QueryCacheKey
366+
providedTags: readonly FullTagDescription<string>[]
367+
}>(),
368+
},
369+
},
329370
extraReducers(builder) {
330371
builder
331372
.addCase(
@@ -371,27 +412,13 @@ export function buildSlice({
371412
)
372413
const { queryCacheKey } = action.meta.arg
373414

374-
for (const tagTypeSubscriptions of Object.values(draft)) {
375-
for (const idSubscriptions of Object.values(
376-
tagTypeSubscriptions
377-
)) {
378-
const foundAt = idSubscriptions.indexOf(queryCacheKey)
379-
if (foundAt !== -1) {
380-
idSubscriptions.splice(foundAt, 1)
381-
}
382-
}
383-
}
384-
385-
for (const { type, id } of providedTags) {
386-
const subscribedQueries = ((draft[type] ??= {})[
387-
id || '__internal_without_id'
388-
] ??= [])
389-
const alreadySubscribed =
390-
subscribedQueries.includes(queryCacheKey)
391-
if (!alreadySubscribed) {
392-
subscribedQueries.push(queryCacheKey)
393-
}
394-
}
415+
invalidationSlice.caseReducers.updateProvidedBy(
416+
draft,
417+
invalidationSlice.actions.updateProvidedBy({
418+
queryCacheKey,
419+
providedTags,
420+
})
421+
)
395422
}
396423
)
397424
},
@@ -497,6 +524,7 @@ export function buildSlice({
497524
...subscriptionSlice.actions,
498525
...internalSubscriptionsSlice.actions,
499526
...mutationSlice.actions,
527+
...invalidationSlice.actions,
500528
/** @deprecated has been renamed to `removeMutationResult` */
501529
unsubscribeMutationResult: mutationSlice.actions.removeMutationResult,
502530
resetApiState,

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

+55-20
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import type {
2020
QueryArgFrom,
2121
QueryDefinition,
2222
ResultTypeFrom,
23+
FullTagDescription,
2324
} from '../endpointDefinitions'
2425
import { isQueryDefinition } from '../endpointDefinitions'
2526
import { calculateProvidedBy } from '../endpointDefinitions'
@@ -164,7 +165,8 @@ export type PatchQueryDataThunk<
164165
> = <EndpointName extends QueryKeys<Definitions>>(
165166
endpointName: EndpointName,
166167
args: QueryArgFrom<Definitions[EndpointName]>,
167-
patches: readonly Patch[]
168+
patches: readonly Patch[],
169+
updateProvided?: boolean
168170
) => ThunkAction<void, PartialState, any, AnyAction>
169171

170172
export type UpdateQueryDataThunk<
@@ -173,7 +175,8 @@ export type UpdateQueryDataThunk<
173175
> = <EndpointName extends QueryKeys<Definitions>>(
174176
endpointName: EndpointName,
175177
args: QueryArgFrom<Definitions[EndpointName]>,
176-
updateRecipe: Recipe<ResultTypeFrom<Definitions[EndpointName]>>
178+
updateRecipe: Recipe<ResultTypeFrom<Definitions[EndpointName]>>,
179+
updateProvided?: boolean
177180
) => ThunkAction<PatchCollection, PartialState, any, AnyAction>
178181

179182
export type UpsertQueryDataThunk<
@@ -222,57 +225,87 @@ export function buildThunks<
222225
context: { endpointDefinitions },
223226
serializeQueryArgs,
224227
api,
228+
assertTagType,
225229
}: {
226230
baseQuery: BaseQuery
227231
reducerPath: ReducerPath
228232
context: ApiContext<Definitions>
229233
serializeQueryArgs: InternalSerializeQueryArgs
230234
api: Api<BaseQuery, Definitions, ReducerPath, any>
235+
assertTagType: AssertTagTypes
231236
}) {
232237
type State = RootState<any, string, ReducerPath>
233238

234239
const patchQueryData: PatchQueryDataThunk<EndpointDefinitions, State> =
235-
(endpointName, args, patches) => (dispatch) => {
240+
(endpointName, args, patches, updateProvided) => (dispatch, getState) => {
236241
const endpointDefinition = endpointDefinitions[endpointName]
242+
243+
const queryCacheKey = serializeQueryArgs({
244+
queryArgs: args,
245+
endpointDefinition,
246+
endpointName,
247+
})
248+
237249
dispatch(
238-
api.internalActions.queryResultPatched({
239-
queryCacheKey: serializeQueryArgs({
240-
queryArgs: args,
241-
endpointDefinition,
242-
endpointName,
243-
}),
244-
patches,
245-
})
250+
api.internalActions.queryResultPatched({ queryCacheKey, patches })
251+
)
252+
253+
if (!updateProvided) {
254+
return
255+
}
256+
257+
const newValue = api.endpoints[endpointName].select(args)(getState())
258+
259+
const providedTags = calculateProvidedBy(
260+
endpointDefinition.providesTags,
261+
newValue.data,
262+
undefined,
263+
args,
264+
{},
265+
assertTagType
266+
)
267+
268+
dispatch(
269+
api.internalActions.updateProvidedBy({ queryCacheKey, providedTags })
246270
)
247271
}
248272

249273
const updateQueryData: UpdateQueryDataThunk<EndpointDefinitions, State> =
250-
(endpointName, args, updateRecipe) => (dispatch, getState) => {
251-
const currentState = (
252-
api.endpoints[endpointName] as ApiEndpointQuery<any, any>
253-
).select(args)(getState())
274+
(endpointName, args, updateRecipe, updateProvided = true) =>
275+
(dispatch, getState) => {
276+
const endpointDefinition = api.endpoints[endpointName]
277+
278+
const currentState = endpointDefinition.select(args)(getState())
279+
254280
let ret: PatchCollection = {
255281
patches: [],
256282
inversePatches: [],
257283
undo: () =>
258284
dispatch(
259-
api.util.patchQueryData(endpointName, args, ret.inversePatches)
285+
api.util.patchQueryData(
286+
endpointName,
287+
args,
288+
ret.inversePatches,
289+
updateProvided
290+
)
260291
),
261292
}
262293
if (currentState.status === QueryStatus.uninitialized) {
263294
return ret
264295
}
296+
let newValue
265297
if ('data' in currentState) {
266298
if (isDraftable(currentState.data)) {
267-
const [, patches, inversePatches] = produceWithPatches(
299+
const [value, patches, inversePatches] = produceWithPatches(
268300
currentState.data,
269301
updateRecipe
270302
)
271303
ret.patches.push(...patches)
272304
ret.inversePatches.push(...inversePatches)
305+
newValue = value
273306
} else {
274-
const value = updateRecipe(currentState.data)
275-
ret.patches.push({ op: 'replace', path: [], value })
307+
newValue = updateRecipe(currentState.data)
308+
ret.patches.push({ op: 'replace', path: [], value: newValue })
276309
ret.inversePatches.push({
277310
op: 'replace',
278311
path: [],
@@ -281,7 +314,9 @@ export function buildThunks<
281314
}
282315
}
283316

284-
dispatch(api.util.patchQueryData(endpointName, args, ret.patches))
317+
dispatch(
318+
api.util.patchQueryData(endpointName, args, ret.patches, updateProvided)
319+
)
285320

286321
return ret
287322
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,7 @@ export const coreModule = (): Module<CoreModule> => ({
518518
context,
519519
api,
520520
serializeQueryArgs,
521+
assertTagType,
521522
})
522523

523524
const { reducer, actions: sliceActions } = buildSlice({

0 commit comments

Comments
 (0)