Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix infinite query fetching when refetchOnMountOrArgChange is true #4795

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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' }],
])
})
})
Loading