diff --git a/packages/toolkit/src/query/core/buildThunks.ts b/packages/toolkit/src/query/core/buildThunks.ts index 22588df9b9..37f6606c7c 100644 --- a/packages/toolkit/src/query/core/buildThunks.ts +++ b/packages/toolkit/src/query/core/buildThunks.ts @@ -633,12 +633,19 @@ export function buildThunks< getState(), arg.queryCacheKey, )?.data as InfiniteData | undefined - // Don't want to use `isForcedQuery` here, because that - // includes `refetchOnMountOrArgChange`. + + // When the arg changes or the user forces a refetch, + // we don't include the `direction` flag. This lets us distinguish + // between actually refetching with a forced query, vs just fetching + // the next page. + const isForcedQueryNeedingRefetch = // arg.forceRefetch + isForcedQuery(arg, getState()) && + !(arg as InfiniteQueryThunkArg).direction const existingData = ( - arg.forceRefetch || !cachedData ? blankData : cachedData + isForcedQueryNeedingRefetch || !cachedData ? blankData : cachedData ) as InfiniteData + // If the thunk specified a direction and we do have at least one page, // fetch the next or previous page if ('direction' in arg && arg.direction && existingData.pages.length) { diff --git a/packages/toolkit/src/query/tests/infiniteQueries.test.ts b/packages/toolkit/src/query/tests/infiniteQueries.test.ts index 0ef99d513e..9d24b611b9 100644 --- a/packages/toolkit/src/query/tests/infiniteQueries.test.ts +++ b/packages/toolkit/src/query/tests/infiniteQueries.test.ts @@ -37,6 +37,7 @@ describe('Infinite queries', () => { } let counters: Record = {} + let queryCounter = 0 const pokemonApi = createApi({ baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }), @@ -149,6 +150,7 @@ describe('Infinite queries', () => { const url = new URL(request.url) const pageString = url.searchParams.get('page') const pageNum = parseInt(pageString || '0') + queryCounter++ const results: Pokemon[] = [ { id: `${pageNum}`, name: `Pokemon ${pageNum}` }, @@ -168,6 +170,7 @@ describe('Infinite queries', () => { counters = {} hitCounter = 0 + queryCounter = 0 process.env.NODE_ENV = 'development' }) @@ -697,6 +700,44 @@ describe('Infinite queries', () => { [{ id: '0', name: 'Pokemon 0' }], [{ id: '1', name: 'Pokemon 1' }], ]) + + expect(queryCounter).toBe(2) + + const entry2InitialLoad = await storeRef.store.dispatch( + pokemonApiWithRefetch.endpoints.getInfinitePokemon.initiate('water', {}), + ) + + checkResultData(entry2InitialLoad, [[{ id: '0', name: 'Pokemon 0' }]]) + + expect(queryCounter).toBe(3) + + const entry2SecondPage = await storeRef.store.dispatch( + pokemonApiWithRefetch.endpoints.getInfinitePokemon.initiate('water', { + direction: 'forward', + }), + ) + checkResultData(entry2SecondPage, [ + [{ id: '0', name: 'Pokemon 0' }], + [{ id: '1', name: 'Pokemon 1' }], + ]) + + expect(queryCounter).toBe(4) + + // Should now be able to switch back to the first query. + // The hooks dispatch on arg change without a direction. + // That should trigger a refetch of the first query, meaning two requests. + // It should also _replace_ the existing results, rather than appending + // duplicate entries ([0, 1, 0, 1]) + const entry1Refetched = await storeRef.store.dispatch( + pokemonApiWithRefetch.endpoints.getInfinitePokemon.initiate('fire', {}), + ) + + checkResultData(entry1Refetched, [ + [{ id: '0', name: 'Pokemon 0' }], + [{ id: '1', name: 'Pokemon 1' }], + ]) + + expect(queryCounter).toBe(6) }) test('Works with cache manipulation utils', async () => {