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: store directions in infinite query result structure #7258

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
99 changes: 95 additions & 4 deletions packages/query-core/src/__tests__/infiniteQueryBehavior.test.tsx
Expand Up @@ -77,7 +77,7 @@ describe('InfiniteQueryBehavior', () => {
await waitFor(() =>
expect(observerResult).toMatchObject({
isFetching: false,
data: { pages: [1], pageParams: [1] },
data: { pages: [1], pageParams: [1], directions: ['forward'] },
}),
)

Expand All @@ -104,7 +104,11 @@ describe('InfiniteQueryBehavior', () => {

expect(observerResult).toMatchObject({
isFetching: false,
data: { pages: [1, 2], pageParams: [1, 2] },
data: {
pages: [1, 2],
pageParams: [1, 2],
directions: ['forward', 'forward'],
},
})

queryFnSpy.mockClear()
Expand All @@ -123,7 +127,11 @@ describe('InfiniteQueryBehavior', () => {
// Only first two pages should be in the data
expect(observerResult).toMatchObject({
isFetching: false,
data: { pages: [0, 1], pageParams: [0, 1] },
data: {
pages: [0, 1],
pageParams: [0, 1],
directions: ['backward', 'forward'],
},
})

queryFnSpy.mockClear()
Expand Down Expand Up @@ -174,7 +182,7 @@ describe('InfiniteQueryBehavior', () => {
queryKey: key,
pageParam: 0,
meta: undefined,
direction: 'forward',
direction: 'backward',
signal: abortSignal,
})

Expand All @@ -189,6 +197,89 @@ describe('InfiniteQueryBehavior', () => {
unsubscribe()
})

test('InfiniteQueryBehavior should use direction of the first page when refetching', async () => {
const key = queryKey()

const queryFnSpy = vi.fn().mockImplementation(({ pageParam }) => {
return pageParam
})

const observer = new InfiniteQueryObserver<number>(queryClient, {
queryKey: key,
queryFn: queryFnSpy,
getNextPageParam: (lastPage) => lastPage + 1,
getPreviousPageParam: (firstPage) => firstPage - 1,
initialPageParam: 1,
})

let observerResult:
| InfiniteQueryObserverResult<unknown, unknown>
| undefined

const unsubscribe = observer.subscribe((result) => {
observerResult = result
})

// Wait for the first page to be fetched
await waitFor(() =>
expect(observerResult).toMatchObject({
isFetching: false,
data: { pages: [1], pageParams: [1], directions: ['forward'] },
}),
)

expect(queryFnSpy).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
direction: 'forward',
}),
)

queryFnSpy.mockClear()

// Fetch the second page
await observer.fetchPreviousPage()

expect(queryFnSpy).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
direction: 'backward',
}),
)

expect(observerResult).toMatchObject({
isFetching: false,
data: {
pages: [0, 1],
pageParams: [0, 1],
directions: ['backward', 'forward'],
},
})

queryFnSpy.mockClear()

// refetch
await observer.refetch()

expect(queryFnSpy).toHaveBeenCalledTimes(2)

expect(queryFnSpy).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
direction: 'backward',
}),
)

expect(queryFnSpy).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
direction: 'forward',
}),
)

unsubscribe()
})

test('InfiniteQueryBehavior should support query cancellation', async () => {
const key = queryKey()
let abortSignal: AbortSignal | null = null
Expand Down
6 changes: 6 additions & 0 deletions packages/query-core/src/__tests__/queryClient.test.tsx
Expand Up @@ -647,6 +647,7 @@ describe('queryClient', () => {
const data = {
pages: ['data'],
pageParams: [0],
directions: ['forward'],
} as const

const fetchFn: QueryFunction<StrictData, StrictQueryKey, number> = () =>
Expand Down Expand Up @@ -675,6 +676,7 @@ describe('queryClient', () => {
const expected = {
pages: [10],
pageParams: [10],
directions: ['forward'],
}

expect(result).toEqual(expected)
Expand Down Expand Up @@ -704,6 +706,7 @@ describe('queryClient', () => {
expect(result).toEqual({
pages: ['data'],
pageParams: [0],
directions: ['forward'],
})
})

Expand All @@ -721,6 +724,7 @@ describe('queryClient', () => {
expect(result).toEqual({
pages: [10],
pageParams: [10],
directions: ['forward'],
})
})

Expand All @@ -741,6 +745,7 @@ describe('queryClient', () => {
expect(result).toEqual({
pages: ['10', '15', '20'],
pageParams: [10, 15, 20],
directions: ['forward', 'forward', 'forward'],
})
})

Expand All @@ -761,6 +766,7 @@ describe('queryClient', () => {
expect(result).toEqual({
pages: ['10', '15', '20'],
pageParams: [10, 15, 20],
directions: ['forward', 'forward', 'forward'],
})
})
})
Expand Down
18 changes: 16 additions & 2 deletions packages/query-core/src/infiniteQueryBehavior.ts
Expand Up @@ -18,7 +18,12 @@ export function infiniteQueryBehavior<TQueryFnData, TError, TData, TPageParam>(
const direction = context.fetchOptions?.meta?.fetchMore?.direction
const oldPages = context.state.data?.pages || []
const oldPageParams = context.state.data?.pageParams || []
const empty = { pages: [], pageParams: [] }
const oldDirections = context.state.data?.directions || []
const empty = {
pages: [],
pageParams: [],
directions: [],
}
let cancelled = false

const addSignalProperty = (object: unknown) => {
Expand Down Expand Up @@ -68,13 +73,15 @@ export function infiniteQueryBehavior<TQueryFnData, TError, TData, TPageParam>(
return Promise.resolve(data)
}

const currentDirection = previous ? 'backward' : 'forward'

const queryFnContext: OmitKeyof<
QueryFunctionContext<QueryKey, unknown>,
'signal'
> = {
queryKey: context.queryKey,
pageParam: param,
direction: previous ? 'backward' : 'forward',
direction: currentDirection,
meta: context.options.meta,
}

Expand All @@ -90,6 +97,11 @@ export function infiniteQueryBehavior<TQueryFnData, TError, TData, TPageParam>(
return {
pages: addTo(data.pages, page, maxPages),
pageParams: addTo(data.pageParams, param, maxPages),
directions: addTo(
data.directions ?? [],
currentDirection,
maxPages,
),
}
}

Expand All @@ -102,6 +114,7 @@ export function infiniteQueryBehavior<TQueryFnData, TError, TData, TPageParam>(
const oldData = {
pages: oldPages,
pageParams: oldPageParams,
directions: oldDirections,
}
const param = pageParamFn(options, oldData)

Expand All @@ -111,6 +124,7 @@ export function infiniteQueryBehavior<TQueryFnData, TError, TData, TPageParam>(
result = await fetchPage(
empty,
oldPageParams[0] ?? options.initialPageParam,
oldDirections[0] === 'backward',
)

const remainingPages = pages ?? oldPages.length
Expand Down
1 change: 1 addition & 0 deletions packages/query-core/src/types.ts
Expand Up @@ -121,6 +121,7 @@ export type GetNextPageParamFunction<TPageParam, TQueryFnData = unknown> = (
export interface InfiniteData<TData, TPageParam = unknown> {
pages: Array<TData>
pageParams: Array<TPageParam>
directions?: Array<'forward' | 'backward'>
}

export type QueryMeta = Register extends {
Expand Down
18 changes: 13 additions & 5 deletions packages/react-query/src/__tests__/useInfiniteQuery.test.tsx
Expand Up @@ -98,7 +98,7 @@ describe('useInfiniteQuery', () => {
})

expect(states[1]).toEqual({
data: { pages: [0], pageParams: [0] },
data: { pages: [0], pageParams: [0], directions: ['forward'] },
dataUpdatedAt: expect.any(Number),
error: null,
errorUpdatedAt: 0,
Expand Down Expand Up @@ -627,7 +627,9 @@ describe('useInfiniteQuery', () => {

await waitFor(() => rendered.getByText('status: success, idle'))
await waitFor(() =>
rendered.getByText('data: {"pages":[10],"pageParams":[10]}'),
rendered.getByText(
'data: {"pages":[10],"pageParams":[10],"directions":["forward"]}',
),
)

fireEvent.click(rendered.getByRole('button', { name: /refetch/i }))
Expand All @@ -636,7 +638,9 @@ describe('useInfiniteQuery', () => {

await waitFor(() => rendered.getByText('status: success, idle'))
await waitFor(() =>
rendered.getByText('data: {"pages":[10,11],"pageParams":[10,11]}'),
rendered.getByText(
'data: {"pages":[10,11],"pageParams":[10,11],"directions":["forward","forward"]}',
),
)
})

Expand Down Expand Up @@ -923,7 +927,9 @@ describe('useInfiniteQuery', () => {
const rendered = renderWithClient(queryClient, <Page />)

await waitFor(() =>
rendered.getByText('data: {"pages":[0],"pageParams":[0]}'),
rendered.getByText(
'data: {"pages":[0],"pageParams":[0],"directions":["forward"]}',
),
)

fireEvent.click(rendered.getByRole('button', { name: /setPages/i }))
Expand All @@ -937,7 +943,9 @@ describe('useInfiniteQuery', () => {
fireEvent.click(rendered.getByRole('button', { name: /refetch/i }))

await waitFor(() =>
rendered.getByText('data: {"pages":[14,30],"pageParams":[7,15]}'),
rendered.getByText(
'data: {"pages":[14,30],"pageParams":[7,15],"directions":["forward","forward"]}',
),
)
})

Expand Down
Expand Up @@ -120,7 +120,7 @@ describe('useInfiniteQuery', () => {
})

expect(states[1]).toEqual({
data: { pages: [0], pageParams: [0] },
data: { pages: [0], pageParams: [0], directions: ['forward'] },
dataUpdatedAt: expect.any(Number),
error: null,
errorUpdatedAt: 0,
Expand Down
4 changes: 3 additions & 1 deletion packages/vue-query/src/__tests__/useInfiniteQuery.test.ts
Expand Up @@ -4,7 +4,7 @@ import { flushPromises, infiniteFetcher } from './test-utils'

vi.mock('../useQueryClient')

describe('useQuery', () => {
describe('useInfiniteQuery', () => {
test('should properly execute infinite query', async () => {
const { data, fetchNextPage, status } = useInfiniteQuery({
queryKey: ['infiniteQuery'],
Expand All @@ -21,6 +21,7 @@ describe('useQuery', () => {
expect(data.value).toStrictEqual({
pageParams: [0],
pages: ['data on page 0'],
directions: ['forward'],
})
expect(status.value).toStrictEqual('success')

Expand All @@ -31,6 +32,7 @@ describe('useQuery', () => {
expect(data.value).toStrictEqual({
pageParams: [0, 12],
pages: ['data on page 0', 'data on page 12'],
directions: ['forward', 'forward'],
})
expect(status.value).toStrictEqual('success')
})
Expand Down