Skip to content

Commit

Permalink
Allow infinite query invalidation and promise checks (#4812)
Browse files Browse the repository at this point in the history
  • Loading branch information
markerikson authored Jan 12, 2025
1 parent 968db05 commit 91f2cc6
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 27 deletions.
6 changes: 4 additions & 2 deletions packages/toolkit/src/query/core/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,12 @@ import { buildSelectors } from './buildSelectors'
import type { SliceActions, UpsertEntries } from './buildSlice'
import { buildSlice } from './buildSlice'
import type {
AllQueryKeys,
BuildThunksApiEndpointInfiniteQuery,
BuildThunksApiEndpointMutation,
BuildThunksApiEndpointQuery,
PatchQueryDataThunk,
QueryArgFromAnyQueryDefinition,
UpdateQueryDataThunk,
UpsertQueryDataThunk,
} from './buildThunks'
Expand Down Expand Up @@ -167,9 +169,9 @@ export interface ApiModules<
*
* See https://redux-toolkit.js.org/rtk-query/usage/server-side-rendering for details.
*/
getRunningQueryThunk<EndpointName extends QueryKeys<Definitions>>(
getRunningQueryThunk<EndpointName extends AllQueryKeys<Definitions>>(
endpointName: EndpointName,
arg: QueryArgFrom<Definitions[EndpointName]>,
arg: QueryArgFromAnyQueryDefinition<Definitions, EndpointName>,
): ThunkWithReturnValue<
| QueryActionCreatorResult<
Definitions[EndpointName] & { type: 'query' }
Expand Down
8 changes: 7 additions & 1 deletion packages/toolkit/src/query/endpointDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,13 @@ export interface InfiniteQueryExtraOptions<
CacheCollectionQueryExtraOptions {
type: DefinitionType.infinitequery

providesTags?: never
providesTags?: ResultDescription<
TagTypes,
ResultType,
QueryArg,
BaseQueryError<BaseQuery>,
BaseQueryMeta<BaseQuery>
>
/**
* Not to be used. A query should not invalidate tags in the cache.
*/
Expand Down
208 changes: 184 additions & 24 deletions packages/toolkit/src/query/tests/infiniteQueries.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ import {
waitFor,
} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { HttpResponse, http } from 'msw'
import { HttpResponse, delay, http } from 'msw'
import util from 'util'
import type { InfiniteQueryActionCreatorResult } from '@reduxjs/toolkit/query/react'
import type {
InfiniteQueryActionCreatorResult,
QueryCacheKey,
} from '@reduxjs/toolkit/query/react'
import {
QueryStatus,
createApi,
Expand Down Expand Up @@ -101,6 +104,40 @@ describe('Infinite queries', () => {
}),
})

let hitCounter = 0

type HitCounter = { page: number; hitCounter: number }

const countersApi = createApi({
baseQuery: fakeBaseQuery(),
tagTypes: ['Counter'],
endpoints: (build) => ({
counters: build.infiniteQuery<HitCounter, string, number>({
queryFn(page) {
hitCounter++

return { data: { page, hitCounter } }
},
infiniteQueryOptions: {
initialPageParam: 0,
getNextPageParam: (
lastPage,
allPages,
lastPageParam,
allPageParams,
) => lastPageParam + 1,
},
providesTags: ['Counter'],
}),
mutation: build.mutation<null, void>({
queryFn: async () => {
return { data: null }
},
invalidatesTags: ['Counter'],
}),
}),
})

let storeRef = setupApiStore(
pokemonApi,
{ ...actionsReducer },
Expand Down Expand Up @@ -133,6 +170,8 @@ describe('Infinite queries', () => {

counters = {}

hitCounter = 0

process.env.NODE_ENV = 'development'
})

Expand Down Expand Up @@ -404,32 +443,133 @@ describe('Infinite queries', () => {
})

test('refetches all existing pages', async () => {
let hitCounter = 0
const checkResultData = (
result: InfiniteQueryResult,
expectedValues: HitCounter[],
) => {
expect(result.status).toBe(QueryStatus.fulfilled)
if (result.status === QueryStatus.fulfilled) {
expect(result.data.pages).toEqual(expectedValues)
}
}

type HitCounter = { page: number; hitCounter: number }
const storeRef = setupApiStore(
countersApi,
{ ...actionsReducer },
{
withoutTestLifecycles: true,
},
)

const countersApi = createApi({
baseQuery: fakeBaseQuery(),
endpoints: (build) => ({
counters: build.infiniteQuery<HitCounter, string, number>({
queryFn(page) {
hitCounter++
await storeRef.store.dispatch(
countersApi.endpoints.counters.initiate('item', {
initialPageParam: 3,
}),
)

return { data: { page, hitCounter } }
},
infiniteQueryOptions: {
initialPageParam: 0,
getNextPageParam: (
lastPage,
allPages,
lastPageParam,
allPageParams,
) => lastPageParam + 1,
},
}),
await storeRef.store.dispatch(
countersApi.endpoints.counters.initiate('item', {
direction: 'forward',
}),
)

const thirdPromise = storeRef.store.dispatch(
countersApi.endpoints.counters.initiate('item', {
direction: 'forward',
}),
)

const thirdRes = await thirdPromise

checkResultData(thirdRes, [
{ page: 3, hitCounter: 1 },
{ page: 4, hitCounter: 2 },
{ page: 5, hitCounter: 3 },
])

const fourthRes = await thirdPromise.refetch()

checkResultData(fourthRes, [
{ page: 3, hitCounter: 4 },
{ page: 4, hitCounter: 5 },
{ page: 5, hitCounter: 6 },
])
})

test('Refetches on invalidation', async () => {
const checkResultData = (
result: InfiniteQueryResult,
expectedValues: HitCounter[],
) => {
expect(result.status).toBe(QueryStatus.fulfilled)
if (result.status === QueryStatus.fulfilled) {
expect(result.data.pages).toEqual(expectedValues)
}
}

const storeRef = setupApiStore(
countersApi,
{ ...actionsReducer },
{
withoutTestLifecycles: true,
},
)

await storeRef.store.dispatch(
countersApi.endpoints.counters.initiate('item', {
initialPageParam: 3,
}),
)

await storeRef.store.dispatch(
countersApi.endpoints.counters.initiate('item', {
direction: 'forward',
}),
)

const thirdPromise = storeRef.store.dispatch(
countersApi.endpoints.counters.initiate('item', {
direction: 'forward',
}),
)

const thirdRes = await thirdPromise

checkResultData(thirdRes, [
{ page: 3, hitCounter: 1 },
{ page: 4, hitCounter: 2 },
{ page: 5, hitCounter: 3 },
])

await storeRef.store.dispatch(countersApi.endpoints.mutation.initiate())

let entry = countersApi.endpoints.counters.select('item')(
storeRef.store.getState(),
)
const promise = storeRef.store.dispatch(
countersApi.util.getRunningQueryThunk('counters', 'item'),
)
const promises = storeRef.store.dispatch(
countersApi.util.getRunningQueriesThunk(),
)
expect(entry).toMatchObject({
status: 'pending',
})

expect(promise).toBeInstanceOf(Promise)

expect(promises).toEqual([promise])

const finalRes = await promise

checkResultData(finalRes as any, [
{ page: 3, hitCounter: 4 },
{ page: 4, hitCounter: 5 },
{ page: 5, hitCounter: 6 },
])
})

test('Refetches on polling', async () => {
const checkResultData = (
result: InfiniteQueryResult,
expectedValues: HitCounter[],
Expand Down Expand Up @@ -474,9 +614,29 @@ describe('Infinite queries', () => {
{ page: 5, hitCounter: 3 },
])

const fourthRes = await thirdPromise.refetch()
thirdPromise.updateSubscriptionOptions({
pollingInterval: 10,
})

checkResultData(fourthRes, [
await delay(5)

let entry = countersApi.endpoints.counters.select('item')(
storeRef.store.getState(),
)

checkResultData(thirdRes, [
{ page: 3, hitCounter: 1 },
{ page: 4, hitCounter: 2 },
{ page: 5, hitCounter: 3 },
])

await delay(10)

entry = countersApi.endpoints.counters.select('item')(
storeRef.store.getState(),
)

checkResultData(entry as any, [
{ page: 3, hitCounter: 4 },
{ page: 4, hitCounter: 5 },
{ page: 5, hitCounter: 6 },
Expand Down

0 comments on commit 91f2cc6

Please sign in to comment.