Skip to content

Commit 118a342

Browse files
committed
Fix updateQueryData for infinite queries (#4796)
* Fix updateQueryData for infinite queries * Fix upsertQueryData for infinite queries * Fix upsertQueryEntries for infinite queries * Fix types in lifecycle middleware
1 parent 79d093b commit 118a342

File tree

7 files changed

+185
-22
lines changed

7 files changed

+185
-22
lines changed

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,19 @@ export type QueryKeys<Definitions extends EndpointDefinitions> = {
154154
? K
155155
: never
156156
}[keyof Definitions]
157+
158+
export type InfiniteQueryKeys<Definitions extends EndpointDefinitions> = {
159+
[K in keyof Definitions]: Definitions[K] extends InfiniteQueryDefinition<
160+
any,
161+
any,
162+
any,
163+
any,
164+
any
165+
>
166+
? K
167+
: never
168+
}[keyof Definitions]
169+
157170
export type MutationKeys<Definitions extends EndpointDefinitions> = {
158171
[K in keyof Definitions]: Definitions[K] extends MutationDefinition<
159172
any,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ export type QueryActionCreatorResult<
121121
export type InfiniteQueryActionCreatorResult<
122122
D extends InfiniteQueryDefinition<any, any, any, any, any>,
123123
> = Promise<InfiniteQueryResultSelectorResult<D>> & {
124-
arg: QueryArgFrom<D>
124+
arg: InfiniteQueryArgFrom<D>
125125
requestId: string
126126
subscriptionOptions: SubscriptionOptions | undefined
127127
abort(): void

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ export const buildCacheLifecycleHandler: InternalHandlerBuilder = ({
344344
mwApi.dispatch(
345345
api.util.updateQueryData(
346346
endpointName as never,
347-
originalArgs,
347+
originalArgs as never,
348348
updateRecipe,
349349
),
350350
)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,7 @@ export const buildQueryLifecycleHandler: InternalHandlerBuilder = ({
476476
mwApi.dispatch(
477477
api.util.updateQueryData(
478478
endpointName as never,
479-
originalArgs,
479+
originalArgs as never,
480480
updateRecipe,
481481
),
482482
)

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ import type {
2929
} from './apiState'
3030
import { QueryStatus } from './apiState'
3131
import type {
32+
AllQueryKeys,
33+
QueryArgFromAnyQueryDefinition,
34+
DataFromAnyQueryDefinition,
3235
InfiniteQueryThunk,
3336
MutationThunk,
3437
QueryThunk,
@@ -64,11 +67,11 @@ import type { InternalSerializeQueryArgs } from '../defaultSerializeQueryArgs'
6467
*/
6568
export type NormalizedQueryUpsertEntry<
6669
Definitions extends EndpointDefinitions,
67-
EndpointName extends QueryKeys<Definitions>,
70+
EndpointName extends AllQueryKeys<Definitions>,
6871
> = {
6972
endpointName: EndpointName
70-
arg: QueryArgFrom<Definitions[EndpointName]>
71-
value: ResultTypeFrom<Definitions[EndpointName]>
73+
arg: QueryArgFromAnyQueryDefinition<Definitions, EndpointName>
74+
value: DataFromAnyQueryDefinition<Definitions, EndpointName>
7275
}
7376

7477
/**
@@ -89,7 +92,7 @@ export type ProcessedQueryUpsertEntry = {
8992
* A typesafe representation of a util action creator that accepts cache entry descriptions to upsert
9093
*/
9194
export type UpsertEntries<Definitions extends EndpointDefinitions> = (<
92-
EndpointNames extends Array<QueryKeys<Definitions>>,
95+
EndpointNames extends Array<AllQueryKeys<Definitions>>,
9396
>(
9497
entries: [
9598
...{

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

Lines changed: 80 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type {
66
ThunkDispatch,
77
UnknownAction,
88
} from '@reduxjs/toolkit'
9+
import util from 'util'
910
import type { Patch } from 'immer'
1011
import { isDraftable, produceWithPatches } from 'immer'
1112
import type { Api, ApiContext } from '../apiTypes'
@@ -19,8 +20,10 @@ import type {
1920
AssertTagTypes,
2021
EndpointDefinition,
2122
EndpointDefinitions,
23+
InfiniteQueryArgFrom,
2224
InfiniteQueryDefinition,
2325
MutationDefinition,
26+
PageParamFrom,
2427
QueryArgFrom,
2528
QueryDefinition,
2629
ResultTypeFrom,
@@ -40,9 +43,11 @@ import type {
4043
InfiniteQueryConfigOptions,
4144
QueryCacheKey,
4245
InfiniteQueryDirection,
46+
InfiniteQueryKeys,
4347
} from './apiState'
4448
import { QueryStatus } from './apiState'
4549
import type {
50+
InfiniteQueryActionCreatorResult,
4651
QueryActionCreatorResult,
4752
StartInfiniteQueryActionCreatorOptions,
4853
StartQueryActionCreatorOptions,
@@ -194,29 +199,80 @@ export type PatchQueryDataThunk<
194199
updateProvided?: boolean,
195200
) => ThunkAction<void, PartialState, any, UnknownAction>
196201

202+
export type AllQueryKeys<Definitions extends EndpointDefinitions> =
203+
| QueryKeys<Definitions>
204+
| InfiniteQueryKeys<Definitions>
205+
206+
export type QueryArgFromAnyQueryDefinition<
207+
Definitions extends EndpointDefinitions,
208+
EndpointName extends AllQueryKeys<Definitions>,
209+
> =
210+
Definitions[EndpointName] extends InfiniteQueryDefinition<
211+
any,
212+
any,
213+
any,
214+
any,
215+
any
216+
>
217+
? InfiniteQueryArgFrom<Definitions[EndpointName]>
218+
: Definitions[EndpointName] extends QueryDefinition<any, any, any, any>
219+
? QueryArgFrom<Definitions[EndpointName]>
220+
: never
221+
222+
export type DataFromAnyQueryDefinition<
223+
Definitions extends EndpointDefinitions,
224+
EndpointName extends AllQueryKeys<Definitions>,
225+
> =
226+
Definitions[EndpointName] extends InfiniteQueryDefinition<
227+
any,
228+
any,
229+
any,
230+
any,
231+
any
232+
>
233+
? InfiniteData<
234+
ResultTypeFrom<Definitions[EndpointName]>,
235+
PageParamFrom<Definitions[EndpointName]>
236+
>
237+
: Definitions[EndpointName] extends QueryDefinition<any, any, any, any>
238+
? ResultTypeFrom<Definitions[EndpointName]>
239+
: unknown
240+
241+
export type UpsertThunkResult<
242+
Definitions extends EndpointDefinitions,
243+
EndpointName extends AllQueryKeys<Definitions>,
244+
> =
245+
Definitions[EndpointName] extends InfiniteQueryDefinition<
246+
any,
247+
any,
248+
any,
249+
any,
250+
any
251+
>
252+
? InfiniteQueryActionCreatorResult<Definitions[EndpointName]>
253+
: Definitions[EndpointName] extends QueryDefinition<any, any, any, any>
254+
? QueryActionCreatorResult<Definitions[EndpointName]>
255+
: QueryActionCreatorResult<never>
256+
197257
export type UpdateQueryDataThunk<
198258
Definitions extends EndpointDefinitions,
199259
PartialState,
200-
> = <EndpointName extends QueryKeys<Definitions>>(
260+
> = <EndpointName extends AllQueryKeys<Definitions>>(
201261
endpointName: EndpointName,
202-
arg: QueryArgFrom<Definitions[EndpointName]>,
203-
updateRecipe: Recipe<ResultTypeFrom<Definitions[EndpointName]>>,
262+
arg: QueryArgFromAnyQueryDefinition<Definitions, EndpointName>,
263+
updateRecipe: Recipe<DataFromAnyQueryDefinition<Definitions, EndpointName>>,
204264
updateProvided?: boolean,
205265
) => ThunkAction<PatchCollection, PartialState, any, UnknownAction>
206266

207267
export type UpsertQueryDataThunk<
208268
Definitions extends EndpointDefinitions,
209269
PartialState,
210-
> = <EndpointName extends QueryKeys<Definitions>>(
270+
> = <EndpointName extends AllQueryKeys<Definitions>>(
211271
endpointName: EndpointName,
212-
arg: QueryArgFrom<Definitions[EndpointName]>,
213-
value: ResultTypeFrom<Definitions[EndpointName]>,
272+
arg: QueryArgFromAnyQueryDefinition<Definitions, EndpointName>,
273+
value: DataFromAnyQueryDefinition<Definitions, EndpointName>,
214274
) => ThunkAction<
215-
QueryActionCreatorResult<
216-
Definitions[EndpointName] extends QueryDefinition<any, any, any, any>
217-
? Definitions[EndpointName]
218-
: never
219-
>,
275+
UpsertThunkResult<Definitions, EndpointName>,
220276
PartialState,
221277
any,
222278
UnknownAction
@@ -368,7 +424,8 @@ export function buildThunks<
368424

369425
const upsertQueryData: UpsertQueryDataThunk<Definitions, State> =
370426
(endpointName, arg, value) => (dispatch) => {
371-
return dispatch(
427+
type EndpointName = typeof endpointName
428+
const res = dispatch(
372429
(
373430
api.endpoints[endpointName] as ApiEndpointQuery<
374431
QueryDefinition<any, any, any, any, any>,
@@ -381,7 +438,9 @@ export function buildThunks<
381438
data: value,
382439
}),
383440
}),
384-
)
441+
) as UpsertThunkResult<Definitions, EndpointName>
442+
443+
return res
385444
}
386445

387446
// The generic async payload function for all of our thunks
@@ -582,6 +641,14 @@ export function buildThunks<
582641
// Fetch first page
583642
result = await fetchPage(existingData, firstPageParam, maxPages)
584643

644+
if (forceQueryFn) {
645+
// HACK `upsertQueryData` expects the user to pass in the `{pages, pageParams}` structure,
646+
// but `fetchPage` treats that as `pages[0]`. We have to manually un-nest it.
647+
result = {
648+
data: (result.data as InfiniteData<unknown, unknown>).pages[0],
649+
} as QueryReturnValue
650+
}
651+
585652
// Fetch remaining pages
586653
for (let i = 1; i < totalPages; i++) {
587654
const param = getNextPageParam(

packages/toolkit/src/query/tests/infiniteQueries.test.ts

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -523,14 +523,14 @@ describe('Infinite queries', () => {
523523
)
524524

525525
const res1 = storeRef.store.dispatch(
526-
pokemonApi.endpoints.getInfinitePokemon.initiate('fire', {}),
526+
pokemonApiWithRefetch.endpoints.getInfinitePokemon.initiate('fire', {}),
527527
)
528528

529529
const entry1InitialLoad = await res1
530530
checkResultData(entry1InitialLoad, [[{ id: '0', name: 'Pokemon 0' }]])
531531

532532
const res2 = storeRef.store.dispatch(
533-
pokemonApi.endpoints.getInfinitePokemon.initiate('fire', {
533+
pokemonApiWithRefetch.endpoints.getInfinitePokemon.initiate('fire', {
534534
direction: 'forward',
535535
}),
536536
)
@@ -541,4 +541,84 @@ describe('Infinite queries', () => {
541541
[{ id: '1', name: 'Pokemon 1' }],
542542
])
543543
})
544+
545+
test('Works with cache manipulation utils', async () => {
546+
const res1 = storeRef.store.dispatch(
547+
pokemonApi.endpoints.getInfinitePokemon.initiate('fire', {}),
548+
)
549+
550+
const entry1InitialLoad = await res1
551+
checkResultData(entry1InitialLoad, [[{ id: '0', name: 'Pokemon 0' }]])
552+
553+
storeRef.store.dispatch(
554+
pokemonApi.util.updateQueryData('getInfinitePokemon', 'fire', (draft) => {
555+
draft.pages.push([{ id: '1', name: 'Pokemon 1' }])
556+
draft.pageParams.push(1)
557+
}),
558+
)
559+
560+
const selectFire = pokemonApi.endpoints.getInfinitePokemon.select('fire')
561+
const entry1Updated = selectFire(storeRef.store.getState())
562+
563+
expect(entry1Updated.data).toEqual({
564+
pages: [
565+
[{ id: '0', name: 'Pokemon 0' }],
566+
[{ id: '1', name: 'Pokemon 1' }],
567+
],
568+
pageParams: [0, 1],
569+
})
570+
571+
const res2 = storeRef.store.dispatch(
572+
pokemonApi.util.upsertQueryData('getInfinitePokemon', 'water', {
573+
pages: [[{ id: '2', name: 'Pokemon 2' }]],
574+
pageParams: [2],
575+
}),
576+
)
577+
578+
const entry2InitialLoad = await res2
579+
const selectWater = pokemonApi.endpoints.getInfinitePokemon.select('water')
580+
const entry2Updated = selectWater(storeRef.store.getState())
581+
582+
expect(entry2Updated.data).toEqual({
583+
pages: [[{ id: '2', name: 'Pokemon 2' }]],
584+
pageParams: [2],
585+
})
586+
587+
storeRef.store.dispatch(
588+
pokemonApi.util.upsertQueryEntries([
589+
{
590+
endpointName: 'getInfinitePokemon',
591+
arg: 'air',
592+
value: {
593+
pages: [[{ id: '3', name: 'Pokemon 3' }]],
594+
pageParams: [3],
595+
},
596+
},
597+
]),
598+
)
599+
600+
const selectAir = pokemonApi.endpoints.getInfinitePokemon.select('air')
601+
const entry3Initial = selectAir(storeRef.store.getState())
602+
603+
expect(entry3Initial.data).toEqual({
604+
pages: [[{ id: '3', name: 'Pokemon 3' }]],
605+
pageParams: [3],
606+
})
607+
608+
await storeRef.store.dispatch(
609+
pokemonApi.endpoints.getInfinitePokemon.initiate('air', {
610+
direction: 'forward',
611+
}),
612+
)
613+
614+
const entry3Updated = selectAir(storeRef.store.getState())
615+
616+
expect(entry3Updated.data).toEqual({
617+
pages: [
618+
[{ id: '3', name: 'Pokemon 3' }],
619+
[{ id: '4', name: 'Pokemon 4' }],
620+
],
621+
pageParams: [3, 4],
622+
})
623+
})
544624
})

0 commit comments

Comments
 (0)