Skip to content

Commit c9cc8ca

Browse files
authored
Fix infinite query fetching when refetchOnMountOrArgChange is true (#4795)
* Consolidate test assertions * Add failing tests for infinite queries vs refetching * Tweak infinite query forced check
1 parent 79f6ff8 commit c9cc8ca

File tree

3 files changed

+158
-52
lines changed

3 files changed

+158
-52
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -552,8 +552,10 @@ export function buildThunks<
552552
const blankData = { pages: [], pageParams: [] }
553553
const cachedData = getState()[reducerPath].queries[arg.queryCacheKey]
554554
?.data as InfiniteData<unknown, unknown> | undefined
555+
// Don't want to use `isForcedQuery` here, because that
556+
// includes `refetchOnMountOrArgChange`.
555557
const existingData = (
556-
isForcedQuery(arg, getState()) || !cachedData ? blankData : cachedData
558+
arg.forceRefetch || !cachedData ? blankData : cachedData
557559
) as InfiniteData<unknown, unknown>
558560

559561
// If the thunk specified a direction and we do have at least one page,

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

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1720,10 +1720,41 @@ describe('hooks tests', () => {
17201720
}),
17211721
})
17221722

1723+
const pokemonApiWithRefetch = createApi({
1724+
baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
1725+
endpoints: (builder) => ({
1726+
getInfinitePokemon: builder.infiniteQuery<Pokemon, string, number>({
1727+
infiniteQueryOptions: {
1728+
initialPageParam: 0,
1729+
getNextPageParam: (
1730+
lastPage,
1731+
allPages,
1732+
lastPageParam,
1733+
allPageParams,
1734+
) => lastPageParam + 1,
1735+
getPreviousPageParam: (
1736+
firstPage,
1737+
allPages,
1738+
firstPageParam,
1739+
allPageParams,
1740+
) => {
1741+
return firstPageParam > 0 ? firstPageParam - 1 : undefined
1742+
},
1743+
},
1744+
query(pageParam) {
1745+
return `https://example.com/listItems?page=${pageParam}`
1746+
},
1747+
}),
1748+
}),
1749+
refetchOnMountOrArgChange: true,
1750+
})
1751+
17231752
function PokemonList({
1753+
api,
17241754
arg = 'fire',
17251755
initialPageParam = 0,
17261756
}: {
1757+
api: typeof pokemonApi
17271758
arg?: string
17281759
initialPageParam?: number
17291760
}) {
@@ -1733,7 +1764,7 @@ describe('hooks tests', () => {
17331764
isUninitialized,
17341765
fetchNextPage,
17351766
fetchPreviousPage,
1736-
} = pokemonApi.endpoints.getInfinitePokemon.useInfiniteQuery(arg, {
1767+
} = api.endpoints.getInfinitePokemon.useInfiniteQuery(arg, {
17371768
initialPageParam,
17381769
})
17391770

@@ -1782,7 +1813,10 @@ describe('hooks tests', () => {
17821813
)
17831814
})
17841815

1785-
test('useInfiniteQuery fetchNextPage Trigger', async () => {
1816+
test.each([
1817+
['no refetch', pokemonApi],
1818+
['with refetch', pokemonApiWithRefetch],
1819+
])(`useInfiniteQuery %s`, async (_, pokemonApi) => {
17861820
const storeRef = setupApiStore(pokemonApi, undefined, {
17871821
withoutTestLifecycles: true,
17881822
})
@@ -1855,7 +1889,9 @@ describe('hooks tests', () => {
18551889
}
18561890
}
18571891

1858-
const utils = render(<PokemonList />, { wrapper: storeRef.wrapper })
1892+
const utils = render(<PokemonList api={pokemonApi} />, {
1893+
wrapper: storeRef.wrapper,
1894+
})
18591895
checkNumQueries(1)
18601896
checkEntryFlags('fire', {})
18611897
await waitForFetch(true)
@@ -1880,7 +1916,9 @@ describe('hooks tests', () => {
18801916
await waitForFetch()
18811917
checkPageRows(getCurrentRender().withinDOM, 'fire', [0, 1, 2])
18821918

1883-
utils.rerender(<PokemonList arg="water" initialPageParam={3} />)
1919+
utils.rerender(
1920+
<PokemonList api={pokemonApi} arg="water" initialPageParam={3} />,
1921+
)
18841922
checkEntryFlags('water', {})
18851923
await waitForFetch(true)
18861924
checkNumQueries(2)

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

Lines changed: 113 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,28 @@ describe('Infinite queries', () => {
140140
process.env.NODE_ENV = 'test'
141141
})
142142

143+
type InfiniteQueryResult = Awaited<InfiniteQueryActionCreatorResult<any>>
144+
145+
const checkResultData = (
146+
result: InfiniteQueryResult,
147+
expectedValues: Pokemon[][],
148+
) => {
149+
expect(result.status).toBe(QueryStatus.fulfilled)
150+
if (result.status === QueryStatus.fulfilled) {
151+
expect(result.data.pages).toEqual(expectedValues)
152+
}
153+
}
154+
155+
const checkResultLength = (
156+
result: InfiniteQueryResult,
157+
expectedLength: number,
158+
) => {
159+
expect(result.status).toBe(QueryStatus.fulfilled)
160+
if (result.status === QueryStatus.fulfilled) {
161+
expect(result.data.pages).toHaveLength(expectedLength)
162+
}
163+
}
164+
143165
test('Basic infinite query behavior', async () => {
144166
const checkFlags = (
145167
value: unknown,
@@ -168,18 +190,6 @@ describe('Infinite queries', () => {
168190
checkFlags(entry, expectedFlags)
169191
}
170192

171-
type InfiniteQueryResult = Awaited<InfiniteQueryActionCreatorResult<any>>
172-
173-
const checkResultData = (
174-
result: InfiniteQueryResult,
175-
expectedValues: Pokemon[][],
176-
) => {
177-
expect(result.status).toBe(QueryStatus.fulfilled)
178-
if (result.status === QueryStatus.fulfilled) {
179-
expect(result.data.pages).toEqual(expectedValues)
180-
}
181-
}
182-
183193
const res1 = storeRef.store.dispatch(
184194
pokemonApi.endpoints.getInfinitePokemon.initiate('fire', {}),
185195
)
@@ -331,9 +341,7 @@ describe('Infinite queries', () => {
331341
}),
332342
)
333343

334-
if (res.status === QueryStatus.fulfilled) {
335-
expect(res.data.pages).toHaveLength(i)
336-
}
344+
checkResultLength(res, i)
337345
}
338346
})
339347

@@ -344,10 +352,8 @@ describe('Infinite queries', () => {
344352
direction: 'forward',
345353
}),
346354
)
347-
if (res.status === QueryStatus.fulfilled) {
348-
// Should have 1, 2, 3 (repeating) pages
349-
expect(res.data.pages).toHaveLength(Math.min(i, 3))
350-
}
355+
356+
checkResultLength(res, Math.min(i, 3))
351357
}
352358

353359
// Should now have entries 7, 8, 9 after the loop
@@ -358,14 +364,11 @@ describe('Infinite queries', () => {
358364
}),
359365
)
360366

361-
if (res.status === QueryStatus.fulfilled) {
362-
// When we go back 1, we now have 6, 7, 8
363-
expect(res.data.pages).toEqual([
364-
[{ id: '6', name: 'Pokemon 6' }],
365-
[{ id: '7', name: 'Pokemon 7' }],
366-
[{ id: '8', name: 'Pokemon 8' }],
367-
])
368-
}
367+
checkResultData(res, [
368+
[{ id: '6', name: 'Pokemon 6' }],
369+
[{ id: '7', name: 'Pokemon 7' }],
370+
[{ id: '8', name: 'Pokemon 8' }],
371+
])
369372
})
370373

371374
test('validates maxPages during createApi call', async () => {
@@ -403,14 +406,12 @@ describe('Infinite queries', () => {
403406
test('refetches all existing pages', async () => {
404407
let hitCounter = 0
405408

409+
type HitCounter = { page: number; hitCounter: number }
410+
406411
const countersApi = createApi({
407412
baseQuery: fakeBaseQuery(),
408413
endpoints: (build) => ({
409-
counters: build.infiniteQuery<
410-
{ page: number; hitCounter: number },
411-
string,
412-
number
413-
>({
414+
counters: build.infiniteQuery<HitCounter, string, number>({
414415
queryFn(page) {
415416
hitCounter++
416417

@@ -429,6 +430,16 @@ describe('Infinite queries', () => {
429430
}),
430431
})
431432

433+
const checkResultData = (
434+
result: InfiniteQueryResult,
435+
expectedValues: HitCounter[],
436+
) => {
437+
expect(result.status).toBe(QueryStatus.fulfilled)
438+
if (result.status === QueryStatus.fulfilled) {
439+
expect(result.data.pages).toEqual(expectedValues)
440+
}
441+
}
442+
432443
const storeRef = setupApiStore(
433444
countersApi,
434445
{ ...actionsReducer },
@@ -456,23 +467,78 @@ describe('Infinite queries', () => {
456467
)
457468

458469
const thirdRes = await thirdPromise
459-
if (thirdRes.status === QueryStatus.fulfilled) {
460-
expect(thirdRes.data.pages).toEqual([
461-
{ page: 3, hitCounter: 1 },
462-
{ page: 4, hitCounter: 2 },
463-
{ page: 5, hitCounter: 3 },
464-
])
465-
}
470+
471+
checkResultData(thirdRes, [
472+
{ page: 3, hitCounter: 1 },
473+
{ page: 4, hitCounter: 2 },
474+
{ page: 5, hitCounter: 3 },
475+
])
466476

467477
const fourthRes = await thirdPromise.refetch()
468478

469-
if (fourthRes.status === QueryStatus.fulfilled) {
470-
// Refetching should call the query function again for each page
471-
expect(fourthRes.data.pages).toEqual([
472-
{ page: 3, hitCounter: 4 },
473-
{ page: 4, hitCounter: 5 },
474-
{ page: 5, hitCounter: 6 },
475-
])
476-
}
479+
checkResultData(fourthRes, [
480+
{ page: 3, hitCounter: 4 },
481+
{ page: 4, hitCounter: 5 },
482+
{ page: 5, hitCounter: 6 },
483+
])
484+
})
485+
486+
test('can fetch pages with refetchOnMountOrArgChange active', async () => {
487+
const pokemonApiWithRefetch = createApi({
488+
baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
489+
endpoints: (builder) => ({
490+
getInfinitePokemon: builder.infiniteQuery<Pokemon[], string, number>({
491+
infiniteQueryOptions: {
492+
initialPageParam: 0,
493+
getNextPageParam: (
494+
lastPage,
495+
allPages,
496+
// Page param type should be `number`
497+
lastPageParam,
498+
allPageParams,
499+
) => lastPageParam + 1,
500+
getPreviousPageParam: (
501+
firstPage,
502+
allPages,
503+
firstPageParam,
504+
allPageParams,
505+
) => {
506+
return firstPageParam > 0 ? firstPageParam - 1 : undefined
507+
},
508+
},
509+
query(pageParam) {
510+
return `https://example.com/listItems?page=${pageParam}`
511+
},
512+
}),
513+
}),
514+
refetchOnMountOrArgChange: true,
515+
})
516+
517+
const storeRef = setupApiStore(
518+
pokemonApiWithRefetch,
519+
{ ...actionsReducer },
520+
{
521+
withoutTestLifecycles: true,
522+
},
523+
)
524+
525+
const res1 = storeRef.store.dispatch(
526+
pokemonApi.endpoints.getInfinitePokemon.initiate('fire', {}),
527+
)
528+
529+
const entry1InitialLoad = await res1
530+
checkResultData(entry1InitialLoad, [[{ id: '0', name: 'Pokemon 0' }]])
531+
532+
const res2 = storeRef.store.dispatch(
533+
pokemonApi.endpoints.getInfinitePokemon.initiate('fire', {
534+
direction: 'forward',
535+
}),
536+
)
537+
538+
const entry1SecondPage = await res2
539+
checkResultData(entry1SecondPage, [
540+
[{ id: '0', name: 'Pokemon 0' }],
541+
[{ id: '1', name: 'Pokemon 1' }],
542+
])
477543
})
478544
})

0 commit comments

Comments
 (0)