Skip to content

Commit

Permalink
Fix infinite query fetching when refetchOnMountOrArgChange is true (#…
Browse files Browse the repository at this point in the history
…4795)

* Consolidate test assertions

* Add failing tests for infinite queries vs refetching

* Tweak infinite query forced check
  • Loading branch information
markerikson authored Dec 30, 2024
1 parent 79f6ff8 commit c9cc8ca
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 52 deletions.
4 changes: 3 additions & 1 deletion packages/toolkit/src/query/core/buildThunks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -552,8 +552,10 @@ export function buildThunks<
const blankData = { pages: [], pageParams: [] }
const cachedData = getState()[reducerPath].queries[arg.queryCacheKey]
?.data as InfiniteData<unknown, unknown> | undefined
// Don't want to use `isForcedQuery` here, because that
// includes `refetchOnMountOrArgChange`.
const existingData = (
isForcedQuery(arg, getState()) || !cachedData ? blankData : cachedData
arg.forceRefetch || !cachedData ? blankData : cachedData
) as InfiniteData<unknown, unknown>

// If the thunk specified a direction and we do have at least one page,
Expand Down
46 changes: 42 additions & 4 deletions packages/toolkit/src/query/tests/buildHooks.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1720,10 +1720,41 @@ describe('hooks tests', () => {
}),
})

const pokemonApiWithRefetch = createApi({
baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
endpoints: (builder) => ({
getInfinitePokemon: builder.infiniteQuery<Pokemon, string, number>({
infiniteQueryOptions: {
initialPageParam: 0,
getNextPageParam: (
lastPage,
allPages,
lastPageParam,
allPageParams,
) => lastPageParam + 1,
getPreviousPageParam: (
firstPage,
allPages,
firstPageParam,
allPageParams,
) => {
return firstPageParam > 0 ? firstPageParam - 1 : undefined
},
},
query(pageParam) {
return `https://example.com/listItems?page=${pageParam}`
},
}),
}),
refetchOnMountOrArgChange: true,
})

function PokemonList({
api,
arg = 'fire',
initialPageParam = 0,
}: {
api: typeof pokemonApi
arg?: string
initialPageParam?: number
}) {
Expand All @@ -1733,7 +1764,7 @@ describe('hooks tests', () => {
isUninitialized,
fetchNextPage,
fetchPreviousPage,
} = pokemonApi.endpoints.getInfinitePokemon.useInfiniteQuery(arg, {
} = api.endpoints.getInfinitePokemon.useInfiniteQuery(arg, {
initialPageParam,
})

Expand Down Expand Up @@ -1782,7 +1813,10 @@ describe('hooks tests', () => {
)
})

test('useInfiniteQuery fetchNextPage Trigger', async () => {
test.each([
['no refetch', pokemonApi],
['with refetch', pokemonApiWithRefetch],
])(`useInfiniteQuery %s`, async (_, pokemonApi) => {
const storeRef = setupApiStore(pokemonApi, undefined, {
withoutTestLifecycles: true,
})
Expand Down Expand Up @@ -1855,7 +1889,9 @@ describe('hooks tests', () => {
}
}

const utils = render(<PokemonList />, { wrapper: storeRef.wrapper })
const utils = render(<PokemonList api={pokemonApi} />, {
wrapper: storeRef.wrapper,
})
checkNumQueries(1)
checkEntryFlags('fire', {})
await waitForFetch(true)
Expand All @@ -1880,7 +1916,9 @@ describe('hooks tests', () => {
await waitForFetch()
checkPageRows(getCurrentRender().withinDOM, 'fire', [0, 1, 2])

utils.rerender(<PokemonList arg="water" initialPageParam={3} />)
utils.rerender(
<PokemonList api={pokemonApi} arg="water" initialPageParam={3} />,
)
checkEntryFlags('water', {})
await waitForFetch(true)
checkNumQueries(2)
Expand Down
160 changes: 113 additions & 47 deletions packages/toolkit/src/query/tests/infiniteQueries.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,28 @@ describe('Infinite queries', () => {
process.env.NODE_ENV = 'test'
})

type InfiniteQueryResult = Awaited<InfiniteQueryActionCreatorResult<any>>

const checkResultData = (
result: InfiniteQueryResult,
expectedValues: Pokemon[][],
) => {
expect(result.status).toBe(QueryStatus.fulfilled)
if (result.status === QueryStatus.fulfilled) {
expect(result.data.pages).toEqual(expectedValues)
}
}

const checkResultLength = (
result: InfiniteQueryResult,
expectedLength: number,
) => {
expect(result.status).toBe(QueryStatus.fulfilled)
if (result.status === QueryStatus.fulfilled) {
expect(result.data.pages).toHaveLength(expectedLength)
}
}

test('Basic infinite query behavior', async () => {
const checkFlags = (
value: unknown,
Expand Down Expand Up @@ -168,18 +190,6 @@ describe('Infinite queries', () => {
checkFlags(entry, expectedFlags)
}

type InfiniteQueryResult = Awaited<InfiniteQueryActionCreatorResult<any>>

const checkResultData = (
result: InfiniteQueryResult,
expectedValues: Pokemon[][],
) => {
expect(result.status).toBe(QueryStatus.fulfilled)
if (result.status === QueryStatus.fulfilled) {
expect(result.data.pages).toEqual(expectedValues)
}
}

const res1 = storeRef.store.dispatch(
pokemonApi.endpoints.getInfinitePokemon.initiate('fire', {}),
)
Expand Down Expand Up @@ -331,9 +341,7 @@ describe('Infinite queries', () => {
}),
)

if (res.status === QueryStatus.fulfilled) {
expect(res.data.pages).toHaveLength(i)
}
checkResultLength(res, i)
}
})

Expand All @@ -344,10 +352,8 @@ describe('Infinite queries', () => {
direction: 'forward',
}),
)
if (res.status === QueryStatus.fulfilled) {
// Should have 1, 2, 3 (repeating) pages
expect(res.data.pages).toHaveLength(Math.min(i, 3))
}

checkResultLength(res, Math.min(i, 3))
}

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

if (res.status === QueryStatus.fulfilled) {
// When we go back 1, we now have 6, 7, 8
expect(res.data.pages).toEqual([
[{ id: '6', name: 'Pokemon 6' }],
[{ id: '7', name: 'Pokemon 7' }],
[{ id: '8', name: 'Pokemon 8' }],
])
}
checkResultData(res, [
[{ id: '6', name: 'Pokemon 6' }],
[{ id: '7', name: 'Pokemon 7' }],
[{ id: '8', name: 'Pokemon 8' }],
])
})

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

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

const countersApi = createApi({
baseQuery: fakeBaseQuery(),
endpoints: (build) => ({
counters: build.infiniteQuery<
{ page: number; hitCounter: number },
string,
number
>({
counters: build.infiniteQuery<HitCounter, string, number>({
queryFn(page) {
hitCounter++

Expand All @@ -429,6 +430,16 @@ describe('Infinite queries', () => {
}),
})

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 },
Expand Down Expand Up @@ -456,23 +467,78 @@ describe('Infinite queries', () => {
)

const thirdRes = await thirdPromise
if (thirdRes.status === QueryStatus.fulfilled) {
expect(thirdRes.data.pages).toEqual([
{ page: 3, hitCounter: 1 },
{ page: 4, hitCounter: 2 },
{ page: 5, hitCounter: 3 },
])
}

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

const fourthRes = await thirdPromise.refetch()

if (fourthRes.status === QueryStatus.fulfilled) {
// Refetching should call the query function again for each page
expect(fourthRes.data.pages).toEqual([
{ page: 3, hitCounter: 4 },
{ page: 4, hitCounter: 5 },
{ page: 5, hitCounter: 6 },
])
}
checkResultData(fourthRes, [
{ page: 3, hitCounter: 4 },
{ page: 4, hitCounter: 5 },
{ page: 5, hitCounter: 6 },
])
})

test('can fetch pages with refetchOnMountOrArgChange active', async () => {
const pokemonApiWithRefetch = createApi({
baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
endpoints: (builder) => ({
getInfinitePokemon: builder.infiniteQuery<Pokemon[], string, number>({
infiniteQueryOptions: {
initialPageParam: 0,
getNextPageParam: (
lastPage,
allPages,
// Page param type should be `number`
lastPageParam,
allPageParams,
) => lastPageParam + 1,
getPreviousPageParam: (
firstPage,
allPages,
firstPageParam,
allPageParams,
) => {
return firstPageParam > 0 ? firstPageParam - 1 : undefined
},
},
query(pageParam) {
return `https://example.com/listItems?page=${pageParam}`
},
}),
}),
refetchOnMountOrArgChange: true,
})

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

const res1 = storeRef.store.dispatch(
pokemonApi.endpoints.getInfinitePokemon.initiate('fire', {}),
)

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

const res2 = storeRef.store.dispatch(
pokemonApi.endpoints.getInfinitePokemon.initiate('fire', {
direction: 'forward',
}),
)

const entry1SecondPage = await res2
checkResultData(entry1SecondPage, [
[{ id: '0', name: 'Pokemon 0' }],
[{ id: '1', name: 'Pokemon 1' }],
])
})
})

0 comments on commit c9cc8ca

Please sign in to comment.