Skip to content
Open
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
10 changes: 10 additions & 0 deletions packages/toolkit/src/query/react/buildHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2103,6 +2103,16 @@ export function buildHooks<Definitions extends EndpointDefinitions>({

const trigger: LazyInfiniteQueryTrigger<any> = useCallback(
function (arg: unknown, direction: 'forward' | 'backward') {
// If the query is skipped (`arg` is `skipToken`), we must not start a
// request. Unsubscribe from any previous promise and bail out, so that
// e.g. calling `fetchNextPage()` on a skipped hook is a no-op instead
// of firing a request with `skipToken` as the query arg.
if (arg === skipToken) {
unsubscribePromiseRef(promiseRef)
promiseRef.current = undefined
return promiseRef.current as unknown as InfiniteQueryActionCreatorResult<any>
}

let promise: InfiniteQueryActionCreatorResult<any>

batch(() => {
Expand Down
49 changes: 48 additions & 1 deletion packages/toolkit/src/query/tests/buildHooks.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ import {
createListenerMiddleware,
createSlice,
} from '@reduxjs/toolkit'
import type { SubscriptionOptions } from '@reduxjs/toolkit/query/react'
import type {
SkipToken,
SubscriptionOptions,
} from '@reduxjs/toolkit/query/react'
import {
QueryStatus,
createApi,
Expand Down Expand Up @@ -2543,6 +2546,50 @@ describe('hooks tests', () => {
},
)

test.each<[string, string | SkipToken, boolean | undefined]>([
['skip token', skipToken, undefined],
['skip option', 'fire', true],
])(
'useInfiniteQuery does not fetch when `fetchNextPage`/`fetchPreviousPage` are called while skipped via %s',
async (_, arg, skip) => {
const storeRef = setupApiStore(pokemonApi, undefined, {
withoutTestLifecycles: true,
})

function Pokemon() {
const { isFetching, fetchNextPage, fetchPreviousPage } =
pokemonApi.useGetInfinitePokemonInfiniteQuery(arg, { skip })

return (
<div>
<div data-testid="isFetching">{String(isFetching)}</div>
<button onClick={() => fetchNextPage()}>next</button>
<button onClick={() => fetchPreviousPage()}>previous</button>
</div>
)
}

render(<Pokemon />, { wrapper: storeRef.wrapper })

expect(screen.getByTestId('isFetching').textContent).toBe('false')

fireEvent.click(screen.getByText('next'))
fireEvent.click(screen.getByText('previous'))

// Give any (incorrectly) dispatched request a chance to start
await act(async () => {
await delay(10)
})

// A skipped infinite query must not fetch, so no cache entry
// should have been created and `isFetching` should stay `false`.
expect(
Object.keys(storeRef.store.getState().api.queries),
).toHaveLength(0)
expect(screen.getByTestId('isFetching').textContent).toBe('false')
},
)

test('useInfiniteQuery hook option refetchCachedPages: false only refetches first page', async () => {
const storeRef = setupApiStore(pokemonApi, undefined, {
withoutTestLifecycles: true,
Expand Down
Loading