From aa65b556d4d45b7b8a3853bdd1a23bfd70358fd2 Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Sun, 19 Feb 2023 07:57:21 +0100 Subject: [PATCH 01/21] feat: remove manual mode for infinite queries --- docs/react/guides/infinite-queries.md | 51 +---- docs/react/guides/migrating-to-v5.md | 4 + docs/vue/guides/infinite-queries.md | 21 +- .../query-core/src/infiniteQueryBehavior.ts | 28 +-- .../query-core/src/infiniteQueryObserver.ts | 15 +- .../src/tests/infiniteQueryBehavior.test.tsx | 95 --------- packages/query-core/src/types.ts | 2 - .../src/__tests__/useInfiniteQuery.test.tsx | 186 +----------------- .../__tests__/createInfiniteQuery.test.tsx | 185 +---------------- .../src/__tests__/useInfiniteQuery.test.ts | 3 +- 10 files changed, 36 insertions(+), 554 deletions(-) diff --git a/docs/react/guides/infinite-queries.md b/docs/react/guides/infinite-queries.md index a8fb1a6b99..8d4d504be1 100644 --- a/docs/react/guides/infinite-queries.md +++ b/docs/react/guides/infinite-queries.md @@ -103,42 +103,11 @@ function Projects() { When an infinite query becomes `stale` and needs to be refetched, each group is fetched `sequentially`, starting from the first one. This ensures that even if the underlying data is mutated, we're not using stale cursors and potentially getting duplicates or skipping records. If an infinite query's results are ever removed from the queryCache, the pagination restarts at the initial state with only the initial group being requested. -## What if I need to pass custom information to my query function? - -By default, the variable returned from `getNextPageParam` will be supplied to the query function, but in some cases, you may want to override this. You can pass custom variables to the `fetchNextPage` function which will override the default variable like so: - -[//]: # 'Example3' - -```tsx -function Projects() { - const fetchProjects = ({ pageParam = 0 }) => - fetch('/api/projects?cursor=' + pageParam) - - const { - status, - data, - isFetching, - isFetchingNextPage, - fetchNextPage, - hasNextPage, - } = useInfiniteQuery({ - queryKey: ['projects'], - queryFn: fetchProjects, - getNextPageParam: (lastPage, pages) => lastPage.nextCursor, - }) - - // Pass your own page param - const skipToCursor50 = () => fetchNextPage({ pageParam: 50 }) -} -``` - -[//]: # 'Example3' - ## What if I want to implement a bi-directional infinite list? Bi-directional lists can be implemented by using the `getPreviousPageParam`, `fetchPreviousPage`, `hasPreviousPage` and `isFetchingPreviousPage` properties and functions. -[//]: # 'Example4' +[//]: # 'Example3' ```tsx useInfiniteQuery({ @@ -149,13 +118,13 @@ useInfiniteQuery({ }) ``` -[//]: # 'Example4' +[//]: # 'Example3' ## What if I want to show the pages in reversed order? Sometimes you may want to show the pages in reversed order. If this is case, you can use the `select` option: -[//]: # 'Example5' +[//]: # 'Example4' ```tsx useInfiniteQuery({ @@ -168,13 +137,13 @@ useInfiniteQuery({ }) ``` -[//]: # 'Example5' +[//]: # 'Example4' ## What if I want to manually update the infinite query? Manually removing first page: -[//]: # 'Example6' +[//]: # 'Example5' ```tsx queryClient.setQueryData(['projects'], (data) => ({ @@ -183,11 +152,11 @@ queryClient.setQueryData(['projects'], (data) => ({ })) ``` -[//]: # 'Example6' +[//]: # 'Example5' Manually removing a single value from an individual page: -[//]: # 'Example7' +[//]: # 'Example6' ```tsx const newPagesArray = @@ -201,11 +170,11 @@ queryClient.setQueryData(['projects'], (data) => ({ })) ``` -[//]: # 'Example7' +[//]: # 'Example6' Make sure to keep the same data structure of pages and pageParams! -[//]: # 'Example8' +[//]: # 'Example7' ## What if I want to limit the number of pages? @@ -219,7 +188,7 @@ This is made possible by using the `maxPages` option in conjunction with `getNex In the following example only 3 pages are kept in the query data pages array. If a refetch is needed, only 3 pages will be refetched sequentially. -[//]: # 'Example9' +[//]: # 'Example7' ```tsx useInfiniteQuery({ diff --git a/docs/react/guides/migrating-to-v5.md b/docs/react/guides/migrating-to-v5.md index 281e431e6c..4e9fe40129 100644 --- a/docs/react/guides/migrating-to-v5.md +++ b/docs/react/guides/migrating-to-v5.md @@ -265,6 +265,10 @@ However, refetching all pages might lead to UI inconsistencies. Also, this optio The v5 includes a new `maxPages` option for infinite queries to limit the number of pages to store in the query data and to refetch. This new feature handles the use cases initially identified for the `refetchPage` page feature without the related issues. +### Manual mode for infinite queries has been removed + +Previously, we've allowed to overwrite the `pageParams` that would be returned from `getNextPageParam` or `getPreviousPageParam` by passing a `pageParam` value directly to `fetchNextPage` or `fetchPreviousPage`. This feature didn't work at all with refetches and wasn't widely known or used. + [//]: # 'FrameworkBreakingChanges' ## React Query Breaking Changes diff --git a/docs/vue/guides/infinite-queries.md b/docs/vue/guides/infinite-queries.md index c34c75f1df..a6e22cea01 100644 --- a/docs/vue/guides/infinite-queries.md +++ b/docs/vue/guides/infinite-queries.md @@ -54,24 +54,7 @@ const { ``` [//]: # 'Example' -[//]: # 'Example3' - -```tsx -const fetchProjects = ({ pageParam = 0 }) => - fetch('/api/projects?cursor=' + pageParam) - -const { fetchNextPage } = useInfiniteQuery({ - queryKey: ['projects'], - queryFn: fetchProjects, - getNextPageParam: (lastPage, pages) => lastPage.nextCursor, -}) - -// Pass your own page param -const skipToCursor50 = () => fetchNextPage({ pageParam: 50 }) -``` - -[//]: # 'Example3' -[//]: # 'Example7' +[//]: # 'Example6' ```tsx const newPagesArray = @@ -85,4 +68,4 @@ queryClient.setQueryData(['projects'], (data) => ({ })) ``` -[//]: # 'Example7' +[//]: # 'Example6' diff --git a/packages/query-core/src/infiniteQueryBehavior.ts b/packages/query-core/src/infiniteQueryBehavior.ts index 3883a87484..a0cae1cdc5 100644 --- a/packages/query-core/src/infiniteQueryBehavior.ts +++ b/packages/query-core/src/infiniteQueryBehavior.ts @@ -11,7 +11,6 @@ export function infiniteQueryBehavior< onFetch: (context) => { context.fetchFn = () => { const fetchMore = context.fetchOptions?.meta?.fetchMore - const pageParam = fetchMore?.pageParam const isFetchingNextPage = fetchMore?.direction === 'forward' const isFetchingPreviousPage = fetchMore?.direction === 'backward' const oldPages = context.state.data?.pages || [] @@ -60,7 +59,6 @@ export function infiniteQueryBehavior< // Create function to fetch a page const fetchPage = ( pages: unknown[], - manual?: boolean, param?: unknown, previous?: boolean, ): Promise => { @@ -68,7 +66,7 @@ export function infiniteQueryBehavior< return Promise.reject() } - if (typeof param === 'undefined' && !manual && pages.length) { + if (typeof param === 'undefined' && pages.length) { return Promise.resolve(pages) } @@ -98,38 +96,28 @@ export function infiniteQueryBehavior< // Fetch next page? else if (isFetchingNextPage) { - const manual = typeof pageParam !== 'undefined' - const param = manual - ? pageParam - : getNextPageParam(context.options, oldPages) - promise = fetchPage(oldPages, manual, param) + const param = getNextPageParam(context.options, oldPages) + promise = fetchPage(oldPages, param) } // Fetch previous page? else if (isFetchingPreviousPage) { - const manual = typeof pageParam !== 'undefined' - const param = manual - ? pageParam - : getPreviousPageParam(context.options, oldPages) - promise = fetchPage(oldPages, manual, param, true) + const param = getPreviousPageParam(context.options, oldPages) + promise = fetchPage(oldPages, param, true) } // Refetch pages else { newPageParams = [] - const manual = typeof context.options.getNextPageParam === 'undefined' - // Fetch first page - promise = fetchPage([], manual, oldPageParams[0]) + promise = fetchPage([], oldPageParams[0]) // Fetch remaining pages for (let i = 1; i < oldPages.length; i++) { promise = promise.then((pages) => { - const param = manual - ? oldPageParams[i] - : getNextPageParam(context.options, pages) - return fetchPage(pages, manual, param) + const param = getNextPageParam(context.options, pages) + return fetchPage(pages, param) }) } } diff --git a/packages/query-core/src/infiniteQueryObserver.ts b/packages/query-core/src/infiniteQueryObserver.ts index 76f4e8eeb7..73e432dc07 100644 --- a/packages/query-core/src/infiniteQueryObserver.ts +++ b/packages/query-core/src/infiniteQueryObserver.ts @@ -103,27 +103,24 @@ export class InfiniteQueryObserver< > } - fetchNextPage({ pageParam, ...options }: FetchNextPageOptions = {}): Promise< - InfiniteQueryObserverResult - > { + fetchNextPage( + options: FetchNextPageOptions = {}, + ): Promise> { return this.fetch({ ...options, meta: { - fetchMore: { direction: 'forward', pageParam }, + fetchMore: { direction: 'forward' }, }, }) } - fetchPreviousPage({ - pageParam, - ...options - }: FetchPreviousPageOptions = {}): Promise< + fetchPreviousPage({ ...options }: FetchPreviousPageOptions = {}): Promise< InfiniteQueryObserverResult > { return this.fetch({ ...options, meta: { - fetchMore: { direction: 'backward', pageParam }, + fetchMore: { direction: 'backward' }, }, }) } diff --git a/packages/query-core/src/tests/infiniteQueryBehavior.test.tsx b/packages/query-core/src/tests/infiniteQueryBehavior.test.tsx index c0719b08fb..0225febb5b 100644 --- a/packages/query-core/src/tests/infiniteQueryBehavior.test.tsx +++ b/packages/query-core/src/tests/infiniteQueryBehavior.test.tsx @@ -180,101 +180,6 @@ describe('InfiniteQueryBehavior', () => { unsubscribe() }) - test('InfiniteQueryBehavior should apply pageParam', async () => { - const key = queryKey() - let abortSignal: AbortSignal | null = null - - const queryFnSpy = jest.fn().mockImplementation(({ pageParam, signal }) => { - abortSignal = signal - return pageParam ?? 1 - }) - - const observer = new InfiniteQueryObserver(queryClient, { - queryKey: key, - queryFn: queryFnSpy, - }) - - let observerResult: - | InfiniteQueryObserverResult - | 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: [undefined] }, - }), - ) - - queryFnSpy.mockClear() - - // Fetch the next page using pageParam - await observer.fetchNextPage({ pageParam: 2 }) - - expect(queryFnSpy).toHaveBeenNthCalledWith(1, { - queryKey: key, - pageParam: 2, - meta: undefined, - signal: abortSignal, - }) - - expect(observerResult).toMatchObject({ - isFetching: false, - data: { pages: [1, 2], pageParams: [undefined, 2] }, - }) - - queryFnSpy.mockClear() - - // Fetch the previous page using pageParam - await observer.fetchPreviousPage({ pageParam: 0 }) - - expect(queryFnSpy).toHaveBeenNthCalledWith(1, { - queryKey: key, - pageParam: 0, - meta: undefined, - signal: abortSignal, - }) - - expect(observerResult).toMatchObject({ - isFetching: false, - data: { pages: [0, 1, 2], pageParams: [0, undefined, 2] }, - }) - - queryFnSpy.mockClear() - - // Refetch pages: old manual page params should be used - await observer.refetch() - - expect(queryFnSpy).toHaveBeenCalledTimes(3) - - expect(queryFnSpy).toHaveBeenNthCalledWith(1, { - queryKey: key, - pageParam: 0, - meta: undefined, - signal: abortSignal, - }) - - expect(queryFnSpy).toHaveBeenNthCalledWith(2, { - queryKey: key, - pageParam: undefined, - meta: undefined, - signal: abortSignal, - }) - - expect(queryFnSpy).toHaveBeenNthCalledWith(3, { - queryKey: key, - pageParam: 2, - meta: undefined, - signal: abortSignal, - }) - - unsubscribe() - }) - test('InfiniteQueryBehavior should support query cancellation', async () => { const key = queryKey() let abortSignal: AbortSignal | null = null diff --git a/packages/query-core/src/types.ts b/packages/query-core/src/types.ts index 11d4300139..5535bcf43c 100644 --- a/packages/query-core/src/types.ts +++ b/packages/query-core/src/types.ts @@ -353,12 +353,10 @@ export interface ResetOptions extends RefetchOptions {} export interface FetchNextPageOptions extends ResultOptions { cancelRefetch?: boolean - pageParam?: unknown } export interface FetchPreviousPageOptions extends ResultOptions { cancelRefetch?: boolean - pageParam?: unknown } export type QueryStatus = 'pending' | 'error' | 'success' diff --git a/packages/react-query/src/__tests__/useInfiniteQuery.test.tsx b/packages/react-query/src/__tests__/useInfiniteQuery.test.tsx index cabfc34582..81638c94d4 100644 --- a/packages/react-query/src/__tests__/useInfiniteQuery.test.tsx +++ b/packages/react-query/src/__tests__/useInfiniteQuery.test.tsx @@ -360,15 +360,14 @@ describe('useInfiniteQuery', () => { pageParams: [...data.pageParams].reverse(), }), notifyOnChangeProps: 'all', + getNextPageParam: () => 1, }) states.push(state) return (
- +
data: {state.data?.pages.join(',') ?? 'null'}
isFetching: {state.isFetching}
@@ -482,113 +481,6 @@ describe('useInfiniteQuery', () => { }) }) - it('should be able to refetch when providing page params manually', async () => { - const key = queryKey() - const states: UseInfiniteQueryResult[] = [] - - function Page() { - const state = useInfiniteQuery({ - queryKey: key, - queryFn: async ({ pageParam = 10 }) => { - await sleep(10) - return Number(pageParam) - }, - }) - - states.push(state) - - return ( -
- - - -
data: {state.data?.pages.join(',') ?? 'null'}
-
isFetching: {String(state.isFetching)}
-
- ) - } - - const rendered = renderWithClient(queryClient, ) - - await waitFor(() => rendered.getByText('data: 10')) - fireEvent.click(rendered.getByRole('button', { name: /fetchNextPage/i })) - - await waitFor(() => rendered.getByText('data: 10,11')) - fireEvent.click( - rendered.getByRole('button', { name: /fetchPreviousPage/i }), - ) - await waitFor(() => rendered.getByText('data: 9,10,11')) - fireEvent.click(rendered.getByRole('button', { name: /refetch/i })) - - await waitFor(() => rendered.getByText('isFetching: false')) - await waitFor(() => expect(states.length).toBe(8)) - - // Initial fetch - expect(states[0]).toMatchObject({ - data: undefined, - isFetching: true, - isFetchingNextPage: false, - isRefetching: false, - }) - // Initial fetch done - expect(states[1]).toMatchObject({ - data: { pages: [10] }, - isFetching: false, - isFetchingNextPage: false, - isRefetching: false, - }) - // Fetch next page - expect(states[2]).toMatchObject({ - data: { pages: [10] }, - isFetching: true, - isFetchingNextPage: true, - isRefetching: false, - }) - // Fetch next page done - expect(states[3]).toMatchObject({ - data: { pages: [10, 11] }, - isFetching: false, - isFetchingNextPage: false, - isRefetching: false, - }) - // Fetch previous page - expect(states[4]).toMatchObject({ - data: { pages: [10, 11] }, - isFetching: true, - isFetchingNextPage: false, - isFetchingPreviousPage: true, - isRefetching: false, - }) - // Fetch previous page done - expect(states[5]).toMatchObject({ - data: { pages: [9, 10, 11] }, - isFetching: false, - isFetchingNextPage: false, - isFetchingPreviousPage: false, - isRefetching: false, - }) - // Refetch - expect(states[6]).toMatchObject({ - data: { pages: [9, 10, 11] }, - isFetching: true, - isFetchingNextPage: false, - isFetchingPreviousPage: false, - isRefetching: true, - }) - // Refetch done - expect(states[7]).toMatchObject({ - data: { pages: [9, 10, 11] }, - isFetching: false, - isFetchingNextPage: false, - isFetchingPreviousPage: false, - isRefetching: false, - }) - }) - it('should be able to refetch when providing page params automatically', async () => { const key = queryKey() const states: UseInfiniteQueryResult[] = [] @@ -1011,80 +903,6 @@ describe('useInfiniteQuery', () => { }) }) - it('should be able to override the cursor in the fetchNextPage callback', async () => { - const key = queryKey() - const states: UseInfiniteQueryResult[] = [] - - function Page() { - const state = useInfiniteQuery({ - queryKey: key, - queryFn: async ({ pageParam = 0 }) => { - await sleep(10) - return Number(pageParam) - }, - getNextPageParam: (lastPage) => lastPage + 1, - notifyOnChangeProps: 'all', - }) - - states.push(state) - - return ( -
-
page0: {state.data?.pages[0]}
-
page1: {state.data?.pages[1]}
- -
- ) - } - - const rendered = renderWithClient(queryClient, ) - - await waitFor(() => { - rendered.getByText('page0: 0') - }) - - fireEvent.click(rendered.getByRole('button', { name: 'Fetch next page' })) - - await waitFor(() => { - rendered.getByText('page1: 5') - }) - - // make sure no additional renders are happening after fetching next page - await sleep(20) - - expect(states.length).toBe(4) - expect(states[0]).toMatchObject({ - hasNextPage: undefined, - data: undefined, - isFetching: true, - isFetchingNextPage: false, - isSuccess: false, - }) - expect(states[1]).toMatchObject({ - hasNextPage: true, - data: { pages: [0] }, - isFetching: false, - isFetchingNextPage: false, - isSuccess: true, - }) - expect(states[2]).toMatchObject({ - hasNextPage: true, - data: { pages: [0] }, - isFetching: true, - isFetchingNextPage: true, - isSuccess: true, - }) - expect(states[3]).toMatchObject({ - hasNextPage: true, - data: { pages: [0, 5] }, - isFetching: false, - isFetchingNextPage: false, - isSuccess: true, - }) - }) - it('should be able to set new pages with the query client', async () => { const key = queryKey() const states: UseInfiniteQueryResult[] = [] diff --git a/packages/solid-query/src/__tests__/createInfiniteQuery.test.tsx b/packages/solid-query/src/__tests__/createInfiniteQuery.test.tsx index 3250fe5036..08c221bd65 100644 --- a/packages/solid-query/src/__tests__/createInfiniteQuery.test.tsx +++ b/packages/solid-query/src/__tests__/createInfiniteQuery.test.tsx @@ -380,6 +380,7 @@ describe('useInfiniteQuery', () => { pageParams: [...data.pageParams].reverse(), }), notifyOnChangeProps: 'all', + getNextPageParam: () => 1, })) createRenderEffect(() => { @@ -388,9 +389,7 @@ describe('useInfiniteQuery', () => { return (
- +
data: {state.data?.pages.join(',') ?? 'null'}
isFetching: {state.isFetching}
@@ -505,117 +504,6 @@ describe('useInfiniteQuery', () => { }) }) - it('should be able to refetch when providing page params manually', async () => { - const key = queryKey() - const states: CreateInfiniteQueryResult[] = [] - - function Page() { - const state = createInfiniteQuery(() => ({ - queryKey: key, - queryFn: async ({ pageParam = 10 }) => { - await sleep(10) - return Number(pageParam) - }, - })) - - createRenderEffect(() => { - states.push({ ...state }) - }) - - return ( -
- - - -
data: {state.data?.pages.join(',') ?? 'null'}
-
isFetching: {String(state.isFetching)}
-
- ) - } - - render(() => ( - - - - )) - - await waitFor(() => screen.getByText('data: 10')) - fireEvent.click(screen.getByRole('button', { name: /fetchNextPage/i })) - - await waitFor(() => screen.getByText('data: 10,11')) - fireEvent.click(screen.getByRole('button', { name: /fetchPreviousPage/i })) - await waitFor(() => screen.getByText('data: 9,10,11')) - fireEvent.click(screen.getByRole('button', { name: /refetch/i })) - - await waitFor(() => screen.getByText('isFetching: false')) - await waitFor(() => expect(states.length).toBe(8)) - - // Initial fetch - expect(states[0]).toMatchObject({ - data: undefined, - isFetching: true, - isFetchingNextPage: false, - isRefetching: false, - }) - // Initial fetch done - expect(states[1]).toMatchObject({ - data: { pages: [10] }, - isFetching: false, - isFetchingNextPage: false, - isRefetching: false, - }) - // Fetch next page - expect(states[2]).toMatchObject({ - data: { pages: [10] }, - isFetching: true, - isFetchingNextPage: true, - isRefetching: false, - }) - // Fetch next page done - expect(states[3]).toMatchObject({ - data: { pages: [10, 11] }, - isFetching: false, - isFetchingNextPage: false, - isRefetching: false, - }) - // Fetch previous page - expect(states[4]).toMatchObject({ - data: { pages: [10, 11] }, - isFetching: true, - isFetchingNextPage: false, - isFetchingPreviousPage: true, - isRefetching: false, - }) - // Fetch previous page done - expect(states[5]).toMatchObject({ - data: { pages: [9, 10, 11] }, - isFetching: false, - isFetchingNextPage: false, - isFetchingPreviousPage: false, - isRefetching: false, - }) - // Refetch - expect(states[6]).toMatchObject({ - data: { pages: [9, 10, 11] }, - isFetching: true, - isFetchingNextPage: false, - isFetchingPreviousPage: false, - isRefetching: true, - }) - // Refetch done - expect(states[7]).toMatchObject({ - data: { pages: [9, 10, 11] }, - isFetching: false, - isFetchingNextPage: false, - isFetchingPreviousPage: false, - isRefetching: false, - }) - }) - it('should be able to refetch when providing page params automatically', async () => { const key = queryKey() const states: CreateInfiniteQueryResult[] = [] @@ -1070,75 +958,6 @@ describe('useInfiniteQuery', () => { }) }) - it('should be able to override the cursor in the fetchNextPage callback', async () => { - const key = queryKey() - const states: CreateInfiniteQueryResult[] = [] - - function Page() { - const state = createInfiniteQuery(() => ({ - queryKey: key, - queryFn: async ({ pageParam = 0 }) => { - await sleep(10) - return Number(pageParam) - }, - - getNextPageParam: (lastPage) => lastPage + 1, - notifyOnChangeProps: 'all', - })) - - createRenderEffect(() => { - states.push({ ...state }) - }) - - createEffect(() => { - const { fetchNextPage } = state - setActTimeout(() => { - fetchNextPage({ pageParam: 5 }) - }, 20) - }) - - return null - } - - render(() => ( - - - - )) - - await sleep(100) - - expect(states.length).toBe(4) - expect(states[0]).toMatchObject({ - hasNextPage: undefined, - data: undefined, - isFetching: true, - isFetchingNextPage: false, - isSuccess: false, - }) - expect(states[1]).toMatchObject({ - hasNextPage: true, - data: { pages: [0] }, - isFetching: false, - isFetchingNextPage: false, - isSuccess: true, - }) - expect(states[2]).toMatchObject({ - hasNextPage: true, - data: { pages: [0] }, - isFetching: true, - isFetchingNextPage: true, - isSuccess: true, - }) - expect(states[3]).toMatchObject({ - hasNextPage: true, - data: { pages: [0, 5] }, - isFetching: false, - isFetchingNextPage: false, - isSuccess: true, - }) - }) - it('should be able to set new pages with the query client', async () => { const key = queryKey() const states: CreateInfiniteQueryResult[] = [] diff --git a/packages/vue-query/src/__tests__/useInfiniteQuery.test.ts b/packages/vue-query/src/__tests__/useInfiniteQuery.test.ts index 38a869c921..839cea7ba4 100644 --- a/packages/vue-query/src/__tests__/useInfiniteQuery.test.ts +++ b/packages/vue-query/src/__tests__/useInfiniteQuery.test.ts @@ -8,6 +8,7 @@ describe('useQuery', () => { const { data, fetchNextPage, status } = useInfiniteQuery({ queryKey: ['infiniteQuery'], queryFn: infiniteFetcher, + getNextPageParam: () => 12, }) expect(data.value).toStrictEqual(undefined) @@ -21,7 +22,7 @@ describe('useQuery', () => { }) expect(status.value).toStrictEqual('success') - fetchNextPage({ pageParam: 12 }) + fetchNextPage() await flushPromises() From d40fde59bb41143b2366ab043272f1d34816f4f2 Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Sun, 19 Feb 2023 08:01:23 +0100 Subject: [PATCH 02/21] refactor: simplify checking for next / previous fetch --- .../query-core/src/infiniteQueryBehavior.ts | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/packages/query-core/src/infiniteQueryBehavior.ts b/packages/query-core/src/infiniteQueryBehavior.ts index a0cae1cdc5..e231d461fa 100644 --- a/packages/query-core/src/infiniteQueryBehavior.ts +++ b/packages/query-core/src/infiniteQueryBehavior.ts @@ -11,8 +11,7 @@ export function infiniteQueryBehavior< onFetch: (context) => { context.fetchFn = () => { const fetchMore = context.fetchOptions?.meta?.fetchMore - const isFetchingNextPage = fetchMore?.direction === 'forward' - const isFetchingPreviousPage = fetchMore?.direction === 'backward' + const direction = fetchMore?.direction const oldPages = context.state.data?.pages || [] const oldPageParams = context.state.data?.pageParams || [] let newPageParams = oldPageParams @@ -94,16 +93,13 @@ export function infiniteQueryBehavior< promise = fetchPage([]) } - // Fetch next page? - else if (isFetchingNextPage) { - const param = getNextPageParam(context.options, oldPages) - promise = fetchPage(oldPages, param) - } - - // Fetch previous page? - else if (isFetchingPreviousPage) { - const param = getPreviousPageParam(context.options, oldPages) - promise = fetchPage(oldPages, param, true) + // fetch next / previous page? + else if (direction) { + const previous = direction === 'backward' + const param = previous + ? getPreviousPageParam(context.options, oldPages) + : getNextPageParam(context.options, oldPages) + promise = fetchPage(oldPages, param, previous) } // Refetch pages From ccf5e763b57d39fc76c445d9cb7ae601dcac6330 Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Sun, 19 Feb 2023 08:03:47 +0100 Subject: [PATCH 03/21] types: better typings for fetchMeta.direction --- packages/query-core/src/query.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/query-core/src/query.ts b/packages/query-core/src/query.ts index 73c2f88825..4e907ade4b 100644 --- a/packages/query-core/src/query.ts +++ b/packages/query-core/src/query.ts @@ -76,7 +76,9 @@ export interface QueryBehavior< export interface FetchOptions { cancelRefetch?: boolean - meta?: any + meta?: { + fetchMore?: { direction: 'forward' | 'backward' } + } } interface FailedAction { From e5abed6d47ae8555e6d25e8543277e47128ba452 Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Sun, 19 Feb 2023 08:12:11 +0100 Subject: [PATCH 04/21] refactor: fix hasNextPage / hasPreviousPage we should always return a boolean here, and according to the docs and the implementation in infiniteQueryBehaviour, we will only stop fetching if we return `undefined` from `getNextPageParam` or `getPreviousPageParam`. The checks for `false` or `null` on this boolean were likely wrong --- .../query-core/src/infiniteQueryBehavior.ts | 32 +++++-------------- packages/query-core/src/types.ts | 4 +-- 2 files changed, 10 insertions(+), 26 deletions(-) diff --git a/packages/query-core/src/infiniteQueryBehavior.ts b/packages/query-core/src/infiniteQueryBehavior.ts index e231d461fa..4cc850c76a 100644 --- a/packages/query-core/src/infiniteQueryBehavior.ts +++ b/packages/query-core/src/infiniteQueryBehavior.ts @@ -145,38 +145,22 @@ export function getPreviousPageParam( /** * Checks if there is a next page. - * Returns `undefined` if it cannot be determined. */ export function hasNextPage( options: QueryOptions, - pages?: unknown, -): boolean | undefined { - if (options.getNextPageParam && Array.isArray(pages)) { - const nextPageParam = getNextPageParam(options, pages) - return ( - typeof nextPageParam !== 'undefined' && - nextPageParam !== null && - nextPageParam !== false - ) - } - return + pages?: unknown[], +): boolean { + if (!pages) return false + return typeof options.getNextPageParam?.(options, pages) !== 'undefined' } /** * Checks if there is a previous page. - * Returns `undefined` if it cannot be determined. */ export function hasPreviousPage( options: QueryOptions, - pages?: unknown, -): boolean | undefined { - if (options.getPreviousPageParam && Array.isArray(pages)) { - const previousPageParam = getPreviousPageParam(options, pages) - return ( - typeof previousPageParam !== 'undefined' && - previousPageParam !== null && - previousPageParam !== false - ) - } - return + pages?: unknown[], +): boolean { + if (!pages) return false + return typeof options.getPreviousPageParam?.(options, pages) !== 'undefined' } diff --git a/packages/query-core/src/types.ts b/packages/query-core/src/types.ts index 5535bcf43c..0b8b8832d6 100644 --- a/packages/query-core/src/types.ts +++ b/packages/query-core/src/types.ts @@ -476,8 +476,8 @@ export interface InfiniteQueryObserverBaseResult< fetchPreviousPage: ( options?: FetchPreviousPageOptions, ) => Promise> - hasNextPage?: boolean - hasPreviousPage?: boolean + hasNextPage: boolean + hasPreviousPage: boolean isFetchingNextPage: boolean isFetchingPreviousPage: boolean } From 0ce134f4355f197ae3c82ff1d9c1d322ba6cc99f Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Sun, 19 Feb 2023 08:25:38 +0100 Subject: [PATCH 05/21] fix: hasNextPage / hasPreviousPage is now always a boolean --- .../query-core/src/infiniteQueryBehavior.ts | 8 ++--- .../src/__tests__/useInfiniteQuery.test.tsx | 30 +++++++++---------- .../__tests__/createInfiniteQuery.test.tsx | 26 ++++++++-------- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/packages/query-core/src/infiniteQueryBehavior.ts b/packages/query-core/src/infiniteQueryBehavior.ts index 4cc850c76a..939552501f 100644 --- a/packages/query-core/src/infiniteQueryBehavior.ts +++ b/packages/query-core/src/infiniteQueryBehavior.ts @@ -150,8 +150,8 @@ export function hasNextPage( options: QueryOptions, pages?: unknown[], ): boolean { - if (!pages) return false - return typeof options.getNextPageParam?.(options, pages) !== 'undefined' + if (!pages || !options.getNextPageParam) return false + return typeof getNextPageParam(options, pages) !== 'undefined' } /** @@ -161,6 +161,6 @@ export function hasPreviousPage( options: QueryOptions, pages?: unknown[], ): boolean { - if (!pages) return false - return typeof options.getPreviousPageParam?.(options, pages) !== 'undefined' + if (!pages || !options.getPreviousPageParam) return false + return typeof getPreviousPageParam(options, pages) !== 'undefined' } diff --git a/packages/react-query/src/__tests__/useInfiniteQuery.test.tsx b/packages/react-query/src/__tests__/useInfiniteQuery.test.tsx index 81638c94d4..ec7ffe8819 100644 --- a/packages/react-query/src/__tests__/useInfiniteQuery.test.tsx +++ b/packages/react-query/src/__tests__/useInfiniteQuery.test.tsx @@ -72,8 +72,8 @@ describe('useInfiniteQuery', () => { errorUpdateCount: 0, fetchNextPage: expect.any(Function), fetchPreviousPage: expect.any(Function), - hasNextPage: undefined, - hasPreviousPage: undefined, + hasNextPage: false, + hasPreviousPage: false, isError: false, isFetched: false, isFetchedAfterMount: false, @@ -106,7 +106,7 @@ describe('useInfiniteQuery', () => { fetchNextPage: expect.any(Function), fetchPreviousPage: expect.any(Function), hasNextPage: true, - hasPreviousPage: undefined, + hasPreviousPage: false, isError: false, isFetched: true, isFetchedAfterMount: true, @@ -445,8 +445,8 @@ describe('useInfiniteQuery', () => { expect(states.length).toBe(4) expect(states[0]).toMatchObject({ data: undefined, - hasNextPage: undefined, - hasPreviousPage: undefined, + hasNextPage: false, + hasPreviousPage: false, isFetching: true, isFetchingNextPage: false, isFetchingPreviousPage: false, @@ -454,7 +454,7 @@ describe('useInfiniteQuery', () => { }) expect(states[1]).toMatchObject({ data: { pages: [10] }, - hasNextPage: undefined, + hasNextPage: false, hasPreviousPage: true, isFetching: false, isFetchingNextPage: false, @@ -463,7 +463,7 @@ describe('useInfiniteQuery', () => { }) expect(states[2]).toMatchObject({ data: { pages: [10] }, - hasNextPage: undefined, + hasNextPage: false, hasPreviousPage: true, isFetching: true, isFetchingNextPage: false, @@ -472,7 +472,7 @@ describe('useInfiniteQuery', () => { }) expect(states[3]).toMatchObject({ data: { pages: [9, 10] }, - hasNextPage: undefined, + hasNextPage: false, hasPreviousPage: true, isFetching: false, isFetchingNextPage: false, @@ -628,7 +628,7 @@ describe('useInfiniteQuery', () => { expect(states.length).toBe(5) expect(states[0]).toMatchObject({ - hasNextPage: undefined, + hasNextPage: false, data: undefined, isFetching: true, isFetchingNextPage: false, @@ -841,7 +841,7 @@ describe('useInfiniteQuery', () => { expect(states.length).toBe(2) expect(states[0]).toMatchObject({ - hasNextPage: undefined, + hasNextPage: false, data: undefined, isFetching: true, isFetchingNextPage: false, @@ -944,7 +944,7 @@ describe('useInfiniteQuery', () => { expect(states.length).toBe(5) expect(states[0]).toMatchObject({ - hasNextPage: undefined, + hasNextPage: false, data: undefined, isFetching: true, isFetchingNextPage: false, @@ -1071,7 +1071,7 @@ describe('useInfiniteQuery', () => { expect(states.length).toBe(2) expect(states[0]).toMatchObject({ data: undefined, - hasNextPage: undefined, + hasNextPage: false, isFetching: true, isFetchingNextPage: false, isSuccess: false, @@ -1189,7 +1189,7 @@ describe('useInfiniteQuery', () => { expect(states.length).toBe(2) expect(states[0]).toMatchObject({ data: undefined, - hasNextPage: undefined, + hasNextPage: false, isFetching: true, isFetchingNextPage: false, isSuccess: false, @@ -1263,7 +1263,7 @@ describe('useInfiniteQuery', () => {