Skip to content

Commit 418906f

Browse files
add infinite query support for updateCachedData (#4905)
* add infinite query support for updateCachedData * Consolidate query definition checks --------- Co-authored-by: Mark Erikson <mark@isquaredsoftware.com>
1 parent d624fa3 commit 418906f

File tree

5 files changed

+205
-14
lines changed

5 files changed

+205
-14
lines changed

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { ThunkDispatch, UnknownAction } from '@reduxjs/toolkit'
22
import type { BaseQueryFn, BaseQueryMeta } from '../../baseQueryTypes'
33
import type { BaseEndpointDefinition } from '../../endpointDefinitions'
4-
import { DefinitionType } from '../../endpointDefinitions'
4+
import { DefinitionType, isAnyQueryDefinition } from '../../endpointDefinitions'
55
import type { QueryCacheKey, RootState } from '../apiState'
66
import type {
77
MutationResultSelectorResult,
@@ -328,9 +328,7 @@ export const buildCacheLifecycleHandler: InternalHandlerBuilder = ({
328328
cacheDataLoaded.catch(() => {})
329329
lifecycleMap[queryCacheKey] = lifecycle
330330
const selector = (api.endpoints[endpointName] as any).select(
331-
endpointDefinition.type === DefinitionType.query
332-
? originalArgs
333-
: queryCacheKey,
331+
isAnyQueryDefinition(endpointDefinition) ? originalArgs : queryCacheKey,
334332
)
335333

336334
const extra = mwApi.dispatch((_, __, extra) => extra)
@@ -339,7 +337,7 @@ export const buildCacheLifecycleHandler: InternalHandlerBuilder = ({
339337
getCacheEntry: () => selector(mwApi.getState()),
340338
requestId,
341339
extra,
342-
updateCachedData: (endpointDefinition.type === DefinitionType.query
340+
updateCachedData: (isAnyQueryDefinition(endpointDefinition)
343341
? (updateRecipe: Recipe<any>) =>
344342
mwApi.dispatch(
345343
api.util.updateQueryData(

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type {
33
BaseQueryFn,
44
BaseQueryMeta,
55
} from '../../baseQueryTypes'
6-
import { DefinitionType } from '../../endpointDefinitions'
6+
import { DefinitionType, isAnyQueryDefinition } from '../../endpointDefinitions'
77
import type { Recipe } from '../buildThunks'
88
import { isFulfilled, isPending, isRejected } from '../rtkImports'
99
import type {
@@ -459,9 +459,7 @@ export const buildQueryLifecycleHandler: InternalHandlerBuilder = ({
459459
queryFulfilled.catch(() => {})
460460
lifecycleMap[requestId] = lifecycle
461461
const selector = (api.endpoints[endpointName] as any).select(
462-
endpointDefinition.type === DefinitionType.query
463-
? originalArgs
464-
: requestId,
462+
isAnyQueryDefinition(endpointDefinition) ? originalArgs : requestId,
465463
)
466464

467465
const extra = mwApi.dispatch((_, __, extra) => extra)
@@ -470,7 +468,7 @@ export const buildQueryLifecycleHandler: InternalHandlerBuilder = ({
470468
getCacheEntry: () => selector(mwApi.getState()),
471469
requestId,
472470
extra,
473-
updateCachedData: (endpointDefinition.type === DefinitionType.query
471+
updateCachedData: (isAnyQueryDefinition(endpointDefinition)
474472
? (updateRecipe: Recipe<any>) =>
475473
mwApi.dispatch(
476474
api.util.updateQueryData(

packages/toolkit/src/query/endpointDefinitions.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -912,6 +912,14 @@ export function isInfiniteQueryDefinition(
912912
return e.type === DefinitionType.infinitequery
913913
}
914914

915+
export function isAnyQueryDefinition(
916+
e: EndpointDefinition<any, any, any, any>,
917+
): e is
918+
| QueryDefinition<any, any, any, any>
919+
| InfiniteQueryDefinition<any, any, any, any, any> {
920+
return isQueryDefinition(e) || isInfiniteQueryDefinition(e)
921+
}
922+
915923
export type EndpointBuilder<
916924
BaseQuery extends BaseQueryFn,
917925
TagTypes extends string,

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

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,7 @@ test(`mutation: getCacheEntry`, async () => {
480480
})
481481
})
482482

483-
test('updateCachedData', async () => {
483+
test('query: updateCachedData', async () => {
484484
const trackCalls = vi.fn()
485485

486486
const extended = api.injectEndpoints({
@@ -551,6 +551,95 @@ test('updateCachedData', async () => {
551551
})
552552
})
553553

554+
test('updateCachedData - infinite query', async () => {
555+
const trackCalls = vi.fn()
556+
557+
const extended = api.injectEndpoints({
558+
overrideExisting: true,
559+
endpoints: (build) => ({
560+
infiniteInjected: build.infiniteQuery<{ value: string }, string, number>({
561+
query: () => '/success',
562+
infiniteQueryOptions: {
563+
initialPageParam: 1,
564+
getNextPageParam: (
565+
lastPage,
566+
allPages,
567+
lastPageParam,
568+
allPageParams,
569+
) => lastPageParam + 1,
570+
},
571+
async onCacheEntryAdded(
572+
arg,
573+
{
574+
dispatch,
575+
getState,
576+
getCacheEntry,
577+
updateCachedData,
578+
cacheEntryRemoved,
579+
cacheDataLoaded,
580+
},
581+
) {
582+
expect(getCacheEntry().data).toEqual(undefined)
583+
// calling `updateCachedData` when there is no data yet should not do anything
584+
updateCachedData((draft) => {
585+
draft.pages = [{ value: 'TEST' }]
586+
draft.pageParams = [1]
587+
trackCalls()
588+
})
589+
expect(trackCalls).not.toHaveBeenCalled()
590+
expect(getCacheEntry().data).toEqual(undefined)
591+
592+
gotFirstValue(await cacheDataLoaded)
593+
594+
expect(getCacheEntry().data).toEqual({
595+
pages: [{ value: 'success' }],
596+
pageParams: [1],
597+
})
598+
updateCachedData((draft) => {
599+
draft.pages = [{ value: 'TEST' }]
600+
draft.pageParams = [1]
601+
trackCalls()
602+
})
603+
expect(trackCalls).toHaveBeenCalledOnce()
604+
expect(getCacheEntry().data).toEqual({
605+
pages: [{ value: 'TEST' }],
606+
pageParams: [1],
607+
})
608+
609+
await cacheEntryRemoved
610+
611+
expect(getCacheEntry().data).toEqual(undefined)
612+
// calling `updateCachedData` when there is no data any more should not do anything
613+
updateCachedData((draft) => {
614+
draft.pages = [{ value: 'TEST' }, { value: 'TEST2' }]
615+
draft.pageParams = [1, 2]
616+
trackCalls()
617+
})
618+
expect(trackCalls).toHaveBeenCalledOnce()
619+
expect(getCacheEntry().data).toEqual(undefined)
620+
621+
onCleanup()
622+
},
623+
}),
624+
}),
625+
})
626+
const promise = storeRef.store.dispatch(
627+
extended.endpoints.infiniteInjected.initiate('arg'),
628+
)
629+
await promise
630+
promise.unsubscribe()
631+
632+
await fakeTimerWaitFor(() => {
633+
expect(gotFirstValue).toHaveBeenCalled()
634+
})
635+
636+
await vi.advanceTimersByTimeAsync(61000)
637+
638+
await fakeTimerWaitFor(() => {
639+
expect(onCleanup).toHaveBeenCalled()
640+
})
641+
})
642+
554643
test('dispatching further actions does not trigger another lifecycle', async () => {
555644
const extended = api.injectEndpoints({
556645
overrideExisting: true,

packages/toolkit/src/query/tests/queryLifecycle.test.tsx

Lines changed: 101 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -342,8 +342,6 @@ test('mutation: getCacheEntry (error)', async () => {
342342
})
343343

344344
test('query: updateCachedData', async () => {
345-
const trackCalls = vi.fn()
346-
347345
const extended = api.injectEndpoints({
348346
overrideExisting: true,
349347
endpoints: (build) => ({
@@ -366,7 +364,7 @@ test('query: updateCachedData', async () => {
366364
})
367365

368366
try {
369-
const val = await queryFulfilled
367+
await queryFulfilled
370368
onSuccess(getCacheEntry().data)
371369
} catch (error) {
372370
updateCachedData((draft) => {
@@ -423,6 +421,106 @@ test('query: updateCachedData', async () => {
423421
onSuccess.mockClear()
424422
})
425423

424+
test('infinite query: updateCachedData', async () => {
425+
const extended = api.injectEndpoints({
426+
overrideExisting: true,
427+
endpoints: (build) => ({
428+
infiniteInjected: build.infiniteQuery<{ value: string }, string, number>({
429+
query: () => '/success',
430+
infiniteQueryOptions: {
431+
initialPageParam: 1,
432+
getNextPageParam: (
433+
lastPage,
434+
allPages,
435+
lastPageParam,
436+
allPageParams,
437+
) => lastPageParam + 1,
438+
},
439+
async onQueryStarted(
440+
arg,
441+
{
442+
dispatch,
443+
getState,
444+
getCacheEntry,
445+
updateCachedData,
446+
queryFulfilled,
447+
},
448+
) {
449+
// calling `updateCachedData` when there is no data yet should not do anything
450+
// but if there is a cache value it will be updated & overwritten by the next successful result
451+
updateCachedData((draft) => {
452+
draft.pages = [{ value: '.' }]
453+
draft.pageParams = [1]
454+
})
455+
456+
try {
457+
await queryFulfilled
458+
onSuccess(getCacheEntry().data)
459+
} catch (error) {
460+
updateCachedData((draft) => {
461+
draft.pages = [{ value: 'success.x' }]
462+
draft.pageParams = [1]
463+
})
464+
onError(getCacheEntry().data)
465+
}
466+
},
467+
}),
468+
}),
469+
})
470+
471+
// request 1: success
472+
expect(onSuccess).not.toHaveBeenCalled()
473+
storeRef.store.dispatch(extended.endpoints.infiniteInjected.initiate('arg'))
474+
475+
await waitFor(() => {
476+
expect(onSuccess).toHaveBeenCalled()
477+
})
478+
expect(onSuccess).toHaveBeenCalledWith({
479+
pages: [{ value: 'success' }],
480+
pageParams: [1],
481+
})
482+
onSuccess.mockClear()
483+
484+
// request 2: error
485+
expect(onError).not.toHaveBeenCalled()
486+
server.use(
487+
http.get(
488+
'https://example.com/success',
489+
() => {
490+
return HttpResponse.json({ value: 'failed' }, { status: 500 })
491+
},
492+
{ once: true },
493+
),
494+
)
495+
storeRef.store.dispatch(
496+
extended.endpoints.infiniteInjected.initiate('arg', { forceRefetch: true }),
497+
)
498+
499+
await waitFor(() => {
500+
expect(onError).toHaveBeenCalled()
501+
})
502+
expect(onError).toHaveBeenCalledWith({
503+
pages: [{ value: 'success.x' }],
504+
pageParams: [1],
505+
})
506+
507+
// request 3: success
508+
expect(onSuccess).not.toHaveBeenCalled()
509+
510+
storeRef.store.dispatch(
511+
extended.endpoints.infiniteInjected.initiate('arg', { forceRefetch: true }),
512+
)
513+
514+
await waitFor(() => {
515+
expect(onSuccess).toHaveBeenCalled()
516+
})
517+
expect(onSuccess).toHaveBeenCalledWith({
518+
pages: [{ value: 'success' }],
519+
pageParams: [1],
520+
})
521+
onSuccess.mockClear()
522+
})
523+
426524
test('query: will only start lifecycle if query is not skipped due to `condition`', async () => {
427525
const extended = api.injectEndpoints({
428526
overrideExisting: true,

0 commit comments

Comments
 (0)