Skip to content

Commit 2a0cd70

Browse files
committed
Allow infinite query invalidation and promise checks (#4821)
1 parent 6fe387c commit 2a0cd70

File tree

3 files changed

+195
-27
lines changed

3 files changed

+195
-27
lines changed

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,12 @@ import { buildSelectors } from './buildSelectors'
5858
import type { SliceActions, UpsertEntries } from './buildSlice'
5959
import { buildSlice } from './buildSlice'
6060
import type {
61+
AllQueryKeys,
6162
BuildThunksApiEndpointInfiniteQuery,
6263
BuildThunksApiEndpointMutation,
6364
BuildThunksApiEndpointQuery,
6465
PatchQueryDataThunk,
66+
QueryArgFromAnyQueryDefinition,
6567
UpdateQueryDataThunk,
6668
UpsertQueryDataThunk,
6769
} from './buildThunks'
@@ -167,9 +169,9 @@ export interface ApiModules<
167169
*
168170
* See https://redux-toolkit.js.org/rtk-query/usage/server-side-rendering for details.
169171
*/
170-
getRunningQueryThunk<EndpointName extends QueryKeys<Definitions>>(
172+
getRunningQueryThunk<EndpointName extends AllQueryKeys<Definitions>>(
171173
endpointName: EndpointName,
172-
arg: QueryArgFrom<Definitions[EndpointName]>,
174+
arg: QueryArgFromAnyQueryDefinition<Definitions, EndpointName>,
173175
): ThunkWithReturnValue<
174176
| QueryActionCreatorResult<
175177
Definitions[EndpointName] & { type: 'query' }

packages/toolkit/src/query/endpointDefinitions.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,13 @@ export interface InfiniteQueryExtraOptions<
595595
CacheCollectionQueryExtraOptions {
596596
type: DefinitionType.infinitequery
597597

598-
providesTags?: never
598+
providesTags?: ResultDescription<
599+
TagTypes,
600+
ResultType,
601+
QueryArg,
602+
BaseQueryError<BaseQuery>,
603+
BaseQueryMeta<BaseQuery>
604+
>
599605
/**
600606
* Not to be used. A query should not invalidate tags in the cache.
601607
*/

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

Lines changed: 184 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@ import {
88
waitFor,
99
} from '@testing-library/react'
1010
import userEvent from '@testing-library/user-event'
11-
import { HttpResponse, http } from 'msw'
11+
import { HttpResponse, delay, http } from 'msw'
1212
import util from 'util'
13-
import type { InfiniteQueryActionCreatorResult } from '@reduxjs/toolkit/query/react'
13+
import type {
14+
InfiniteQueryActionCreatorResult,
15+
QueryCacheKey,
16+
} from '@reduxjs/toolkit/query/react'
1417
import {
1518
QueryStatus,
1619
createApi,
@@ -101,6 +104,40 @@ describe('Infinite queries', () => {
101104
}),
102105
})
103106

107+
let hitCounter = 0
108+
109+
type HitCounter = { page: number; hitCounter: number }
110+
111+
const countersApi = createApi({
112+
baseQuery: fakeBaseQuery(),
113+
tagTypes: ['Counter'],
114+
endpoints: (build) => ({
115+
counters: build.infiniteQuery<HitCounter, string, number>({
116+
queryFn(page) {
117+
hitCounter++
118+
119+
return { data: { page, hitCounter } }
120+
},
121+
infiniteQueryOptions: {
122+
initialPageParam: 0,
123+
getNextPageParam: (
124+
lastPage,
125+
allPages,
126+
lastPageParam,
127+
allPageParams,
128+
) => lastPageParam + 1,
129+
},
130+
providesTags: ['Counter'],
131+
}),
132+
mutation: build.mutation<null, void>({
133+
queryFn: async () => {
134+
return { data: null }
135+
},
136+
invalidatesTags: ['Counter'],
137+
}),
138+
}),
139+
})
140+
104141
let storeRef = setupApiStore(
105142
pokemonApi,
106143
{ ...actionsReducer },
@@ -133,6 +170,8 @@ describe('Infinite queries', () => {
133170

134171
counters = {}
135172

173+
hitCounter = 0
174+
136175
process.env.NODE_ENV = 'development'
137176
})
138177

@@ -404,32 +443,133 @@ describe('Infinite queries', () => {
404443
})
405444

406445
test('refetches all existing pages', async () => {
407-
let hitCounter = 0
446+
const checkResultData = (
447+
result: InfiniteQueryResult,
448+
expectedValues: HitCounter[],
449+
) => {
450+
expect(result.status).toBe(QueryStatus.fulfilled)
451+
if (result.status === QueryStatus.fulfilled) {
452+
expect(result.data.pages).toEqual(expectedValues)
453+
}
454+
}
408455

409-
type HitCounter = { page: number; hitCounter: number }
456+
const storeRef = setupApiStore(
457+
countersApi,
458+
{ ...actionsReducer },
459+
{
460+
withoutTestLifecycles: true,
461+
},
462+
)
410463

411-
const countersApi = createApi({
412-
baseQuery: fakeBaseQuery(),
413-
endpoints: (build) => ({
414-
counters: build.infiniteQuery<HitCounter, string, number>({
415-
queryFn(page) {
416-
hitCounter++
464+
await storeRef.store.dispatch(
465+
countersApi.endpoints.counters.initiate('item', {
466+
initialPageParam: 3,
467+
}),
468+
)
417469

418-
return { data: { page, hitCounter } }
419-
},
420-
infiniteQueryOptions: {
421-
initialPageParam: 0,
422-
getNextPageParam: (
423-
lastPage,
424-
allPages,
425-
lastPageParam,
426-
allPageParams,
427-
) => lastPageParam + 1,
428-
},
429-
}),
470+
await storeRef.store.dispatch(
471+
countersApi.endpoints.counters.initiate('item', {
472+
direction: 'forward',
473+
}),
474+
)
475+
476+
const thirdPromise = storeRef.store.dispatch(
477+
countersApi.endpoints.counters.initiate('item', {
478+
direction: 'forward',
430479
}),
480+
)
481+
482+
const thirdRes = await thirdPromise
483+
484+
checkResultData(thirdRes, [
485+
{ page: 3, hitCounter: 1 },
486+
{ page: 4, hitCounter: 2 },
487+
{ page: 5, hitCounter: 3 },
488+
])
489+
490+
const fourthRes = await thirdPromise.refetch()
491+
492+
checkResultData(fourthRes, [
493+
{ page: 3, hitCounter: 4 },
494+
{ page: 4, hitCounter: 5 },
495+
{ page: 5, hitCounter: 6 },
496+
])
497+
})
498+
499+
test('Refetches on invalidation', async () => {
500+
const checkResultData = (
501+
result: InfiniteQueryResult,
502+
expectedValues: HitCounter[],
503+
) => {
504+
expect(result.status).toBe(QueryStatus.fulfilled)
505+
if (result.status === QueryStatus.fulfilled) {
506+
expect(result.data.pages).toEqual(expectedValues)
507+
}
508+
}
509+
510+
const storeRef = setupApiStore(
511+
countersApi,
512+
{ ...actionsReducer },
513+
{
514+
withoutTestLifecycles: true,
515+
},
516+
)
517+
518+
await storeRef.store.dispatch(
519+
countersApi.endpoints.counters.initiate('item', {
520+
initialPageParam: 3,
521+
}),
522+
)
523+
524+
await storeRef.store.dispatch(
525+
countersApi.endpoints.counters.initiate('item', {
526+
direction: 'forward',
527+
}),
528+
)
529+
530+
const thirdPromise = storeRef.store.dispatch(
531+
countersApi.endpoints.counters.initiate('item', {
532+
direction: 'forward',
533+
}),
534+
)
535+
536+
const thirdRes = await thirdPromise
537+
538+
checkResultData(thirdRes, [
539+
{ page: 3, hitCounter: 1 },
540+
{ page: 4, hitCounter: 2 },
541+
{ page: 5, hitCounter: 3 },
542+
])
543+
544+
await storeRef.store.dispatch(countersApi.endpoints.mutation.initiate())
545+
546+
let entry = countersApi.endpoints.counters.select('item')(
547+
storeRef.store.getState(),
548+
)
549+
const promise = storeRef.store.dispatch(
550+
countersApi.util.getRunningQueryThunk('counters', 'item'),
551+
)
552+
const promises = storeRef.store.dispatch(
553+
countersApi.util.getRunningQueriesThunk(),
554+
)
555+
expect(entry).toMatchObject({
556+
status: 'pending',
431557
})
432558

559+
expect(promise).toBeInstanceOf(Promise)
560+
561+
expect(promises).toEqual([promise])
562+
563+
const finalRes = await promise
564+
565+
checkResultData(finalRes as any, [
566+
{ page: 3, hitCounter: 4 },
567+
{ page: 4, hitCounter: 5 },
568+
{ page: 5, hitCounter: 6 },
569+
])
570+
})
571+
572+
test('Refetches on polling', async () => {
433573
const checkResultData = (
434574
result: InfiniteQueryResult,
435575
expectedValues: HitCounter[],
@@ -474,9 +614,29 @@ describe('Infinite queries', () => {
474614
{ page: 5, hitCounter: 3 },
475615
])
476616

477-
const fourthRes = await thirdPromise.refetch()
617+
thirdPromise.updateSubscriptionOptions({
618+
pollingInterval: 10,
619+
})
478620

479-
checkResultData(fourthRes, [
621+
await delay(5)
622+
623+
let entry = countersApi.endpoints.counters.select('item')(
624+
storeRef.store.getState(),
625+
)
626+
627+
checkResultData(thirdRes, [
628+
{ page: 3, hitCounter: 1 },
629+
{ page: 4, hitCounter: 2 },
630+
{ page: 5, hitCounter: 3 },
631+
])
632+
633+
await delay(10)
634+
635+
entry = countersApi.endpoints.counters.select('item')(
636+
storeRef.store.getState(),
637+
)
638+
639+
checkResultData(entry as any, [
480640
{ page: 3, hitCounter: 4 },
481641
{ page: 4, hitCounter: 5 },
482642
{ page: 5, hitCounter: 6 },

0 commit comments

Comments
 (0)