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..b697284f9b 100644 --- a/docs/react/guides/migrating-to-v5.md +++ b/docs/react/guides/migrating-to-v5.md @@ -248,7 +248,7 @@ This in turn will enable other frameworks to have the same functionality in a fr import { queryClient } from './my-client' const { data } = useQuery( - { + { queryKey: ['users', id], queryFn: () => fetch(...), - context: customContext @@ -265,6 +265,26 @@ 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. +### Infinite queries now need a `defaultPageParam` + +Previously, we've passed `undefined` to the `queryFn` as `pageParam`, and you could assign a default value to the `pageParam` parameter in the `queryFn` function signature. This had the drawback of storing `undefined` in the `queryCache`, which is not serializable. + +Instead, you now have to pass an explicit `defaultPageParam` to the infinite query options. This will be used as the `pageParam` for the first page: + +```diff +useInfiniteQuery({ + queryKey, +- queryFn: ({ pageParam = 0 }) => fetchSomething(pageParam), ++ queryFn: ({ pageParam }) => fetchSomething(pageParam), ++ defaultPageParam: 0, + getNextPageParam: (lastPage) => lastPage.next, +}) +``` + +### 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. This also means that `getNextPagParam` is now required for infinite queries. + [//]: # 'FrameworkBreakingChanges' ## React Query Breaking Changes diff --git a/docs/react/reference/useInfiniteQuery.md b/docs/react/reference/useInfiniteQuery.md index 3abdec53fe..34d112ae42 100644 --- a/docs/react/reference/useInfiniteQuery.md +++ b/docs/react/reference/useInfiniteQuery.md @@ -14,7 +14,8 @@ const { ...result } = useInfiniteQuery({ queryKey, - queryFn: ({ pageParam = 1 }) => fetchPage(pageParam), + queryFn: ({ pageParam }) => fetchPage(pageParam), + defaultPageParam: 1, ...options, getNextPageParam: (lastPage, allPages) => lastPage.nextCursor, getPreviousPageParam: (firstPage, allPages) => firstPage.prevCursor, @@ -30,12 +31,15 @@ The options for `useInfiniteQuery` are identical to the [`useQuery` hook](../ref - The function that the query will use to request data. - Receives a [QueryFunctionContext](../guides/query-functions#queryfunctioncontext) - Must return a promise that will either resolve data or throw an error. - - Make sure you return the data *and* the `pageParam` if needed for use in the props below. -- `getNextPageParam: (lastPage, allPages) => unknown | undefined` +- `defaultPageParam: TPageParam` + - **Required** + - The default page param to use when fetching the first page. +- `getNextPageParam: (lastPage, allPages) => TPageParam | undefined` + - **Required** - When new data is received for this query, this function receives both the last page of the infinite list of data and the full array of all pages. - It should return a **single variable** that will be passed as the last optional parameter to your query function. - Return `undefined` to indicate there is no next page available. -- `getPreviousPageParam: (firstPage, allPages) => unknown | undefined` +- `getPreviousPageParam: (firstPage, allPages) => TPageParam | undefined` - When new data is received for this query, this function receives both the first page of the infinite list of data and the full array of all pages. - It should return a **single variable** that will be passed as the last optional parameter to your query function. - Return `undefined` to indicate there is no previous page available. 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/examples/react/algolia/src/algolia.ts b/examples/react/algolia/src/algolia.ts index baaceb1846..6cc1ef4972 100644 --- a/examples/react/algolia/src/algolia.ts +++ b/examples/react/algolia/src/algolia.ts @@ -16,7 +16,7 @@ type SearchOptions = { export async function search({ indexName, query, - pageParam = 0, + pageParam, hitsPerPage = 10, }: SearchOptions): Promise<{ hits: Hit[]; diff --git a/examples/react/algolia/src/useAlgolia.ts b/examples/react/algolia/src/useAlgolia.ts index c42a3793a7..368e40f90e 100644 --- a/examples/react/algolia/src/useAlgolia.ts +++ b/examples/react/algolia/src/useAlgolia.ts @@ -22,6 +22,7 @@ export default function useAlgolia({ queryKey: ["algolia", indexName, query, hitsPerPage], queryFn: ({ pageParam }) => search({ indexName, query, pageParam, hitsPerPage }), + defaultPageParam: 0, getNextPageParam: (lastPage) => lastPage?.nextPage, staleTime, cacheTime, diff --git a/examples/react/infinite-query-with-max-pages/pages/index.js b/examples/react/infinite-query-with-max-pages/pages/index.js index 4c520a8de5..078f698ea7 100755 --- a/examples/react/infinite-query-with-max-pages/pages/index.js +++ b/examples/react/infinite-query-with-max-pages/pages/index.js @@ -31,10 +31,11 @@ function Example() { hasPreviousPage, } = useInfiniteQuery({ queryKey: ['projects'], - queryFn: async ({ pageParam = 0 }) => { + queryFn: async ({ pageParam }) => { const res = await axios.get('/api/projects?cursor=' + pageParam) return res.data }, + defaultPageParam: 0, getPreviousPageParam: (firstPage) => firstPage.previousId ?? undefined, getNextPageParam: (lastPage) => lastPage.nextId ?? undefined, maxPages: 3, diff --git a/examples/react/load-more-infinite-scroll/pages/index.js b/examples/react/load-more-infinite-scroll/pages/index.js index bcd28b2641..55280adaf4 100755 --- a/examples/react/load-more-infinite-scroll/pages/index.js +++ b/examples/react/load-more-infinite-scroll/pages/index.js @@ -35,10 +35,11 @@ function Example() { hasPreviousPage, } = useInfiniteQuery({ queryKey: ['projects'], - queryFn: async ({ pageParam = 0 }) => { + queryFn: async ({ pageParam }) => { const res = await axios.get('/api/projects?cursor=' + pageParam) return res.data }, + defaultPageParam: 0, getPreviousPageParam: (firstPage) => firstPage.previousId ?? undefined, getNextPageParam: (lastPage) => lastPage.nextId ?? undefined, }) diff --git a/packages/query-core/src/infiniteQueryBehavior.ts b/packages/query-core/src/infiniteQueryBehavior.ts index 3883a87484..b8177954d9 100644 --- a/packages/query-core/src/infiniteQueryBehavior.ts +++ b/packages/query-core/src/infiniteQueryBehavior.ts @@ -1,6 +1,11 @@ import type { QueryBehavior } from './query' import { addToEnd, addToStart } from './utils' -import type { InfiniteData, QueryFunctionContext, QueryOptions } from './types' +import type { + InfiniteData, + InfiniteQueryPageParamsOptions, + QueryFunctionContext, + QueryKey, +} from './types' export function infiniteQueryBehavior< TQueryFnData, @@ -10,10 +15,8 @@ export function infiniteQueryBehavior< return { 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 options = context.options as InfiniteQueryPageParamsOptions + const direction = context.fetchOptions?.meta?.fetchMore?.direction const oldPages = context.state.data?.pages || [] const oldPageParams = context.state.data?.pageParams || [] let newPageParams = oldPageParams @@ -60,19 +63,21 @@ export function infiniteQueryBehavior< // Create function to fetch a page const fetchPage = ( pages: unknown[], - manual?: boolean, - param?: unknown, + param: unknown, previous?: boolean, ): Promise => { if (cancelled) { return Promise.reject() } - if (typeof param === 'undefined' && !manual && pages.length) { + if (typeof param === 'undefined' && pages.length) { return Promise.resolve(pages) } - const queryFnContext: Omit = { + const queryFnContext: Omit< + QueryFunctionContext, + 'signal' + > = { queryKey: context.queryKey, pageParam: param, meta: context.options.meta, @@ -80,7 +85,9 @@ export function infiniteQueryBehavior< addSignalProperty(queryFnContext) - const queryFnResult = queryFn(queryFnContext as QueryFunctionContext) + const queryFnResult = queryFn( + queryFnContext as QueryFunctionContext, + ) const promise = Promise.resolve(queryFnResult).then((page) => buildNewPages(pages, param, page, previous), @@ -93,43 +100,30 @@ export function infiniteQueryBehavior< // Fetch first page? if (!oldPages.length) { - promise = fetchPage([]) - } - - // Fetch next page? - else if (isFetchingNextPage) { - const manual = typeof pageParam !== 'undefined' - const param = manual - ? pageParam - : getNextPageParam(context.options, oldPages) - promise = fetchPage(oldPages, manual, param) + promise = fetchPage([], options.defaultPageParam) } - // 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) + // fetch next / previous page? + else if (direction) { + const previous = direction === 'backward' + const param = previous + ? getPreviousPageParam(options, oldPages) + : getNextPageParam(options, oldPages) + promise = fetchPage(oldPages, param, previous) } // 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(options, pages) + return fetchPage(pages, param) }) } } @@ -145,15 +139,15 @@ export function infiniteQueryBehavior< } } -export function getNextPageParam( - options: QueryOptions, +function getNextPageParam( + options: InfiniteQueryPageParamsOptions, pages: unknown[], ): unknown | undefined { - return options.getNextPageParam?.(pages[pages.length - 1], pages) + return options.getNextPageParam(pages[pages.length - 1], pages) } -export function getPreviousPageParam( - options: QueryOptions, +function getPreviousPageParam( + options: InfiniteQueryPageParamsOptions, pages: unknown[], ): unknown | undefined { return options.getPreviousPageParam?.(pages[0], pages) @@ -161,38 +155,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 + options: InfiniteQueryPageParamsOptions, + pages?: unknown[], +): boolean { + if (!pages) return false + return typeof 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 + options: InfiniteQueryPageParamsOptions, + pages?: unknown[], +): boolean { + if (!pages || !options.getPreviousPageParam) return false + return typeof getPreviousPageParam(options, pages) !== 'undefined' } diff --git a/packages/query-core/src/infiniteQueryObserver.ts b/packages/query-core/src/infiniteQueryObserver.ts index 76f4e8eeb7..12a7c3f39b 100644 --- a/packages/query-core/src/infiniteQueryObserver.ts +++ b/packages/query-core/src/infiniteQueryObserver.ts @@ -25,13 +25,13 @@ type InfiniteQueryObserverListener = ( export class InfiniteQueryObserver< TQueryFnData = unknown, TError = RegisteredError, - TData = TQueryFnData, + TData = InfiniteData, TQueryData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, > extends QueryObserver< TQueryFnData, TError, - InfiniteData, + TData, InfiniteData, TQueryKey > { @@ -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/query.ts b/packages/query-core/src/query.ts index 73c2f88825..4aaea76ff7 100644 --- a/packages/query-core/src/query.ts +++ b/packages/query-core/src/query.ts @@ -43,7 +43,7 @@ export interface QueryState { errorUpdatedAt: number fetchFailureCount: number fetchFailureReason: TError | null - fetchMeta: any + fetchMeta: FetchMeta | null isInvalidated: boolean status: QueryStatus fetchStatus: FetchStatus @@ -74,9 +74,13 @@ export interface QueryBehavior< ) => void } +export interface FetchMeta { + fetchMore?: { direction: 'forward' | 'backward' } +} + export interface FetchOptions { cancelRefetch?: boolean - meta?: any + meta?: FetchMeta } interface FailedAction { @@ -87,7 +91,7 @@ interface FailedAction { interface FetchAction { type: 'fetch' - meta?: any + meta?: FetchMeta } interface SuccessAction { @@ -360,7 +364,6 @@ export class Query< // Create query function context const queryFnContext: Omit, 'signal'> = { queryKey: this.queryKey, - pageParam: undefined, meta: this.meta, } diff --git a/packages/query-core/src/queryClient.ts b/packages/query-core/src/queryClient.ts index e3f004e390..80789d9bb0 100644 --- a/packages/query-core/src/queryClient.ts +++ b/packages/query-core/src/queryClient.ts @@ -275,8 +275,15 @@ export class QueryClient { TError = RegisteredError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, + TPageParam = never, >( - options: FetchQueryOptions, + options: FetchQueryOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + >, ): Promise { const defaultedOptions = this.defaultQueryOptions(options) @@ -308,8 +315,15 @@ export class QueryClient { TError = RegisteredError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, >( - options: FetchInfiniteQueryOptions, + options: FetchInfiniteQueryOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + >, ): Promise> { options.behavior = infiniteQueryBehavior() return this.fetchQuery(options) @@ -320,8 +334,15 @@ export class QueryClient { TError = RegisteredError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, >( - options: FetchInfiniteQueryOptions, + options: FetchInfiniteQueryOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + >, ): Promise { return this.fetchInfiniteQuery(options).then(noop).catch(noop) } @@ -405,9 +426,17 @@ export class QueryClient { TData = TQueryFnData, TQueryData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, + TPageParam = never, >( options?: - | QueryObserverOptions + | QueryObserverOptions< + TQueryFnData, + TError, + TData, + TQueryData, + TQueryKey, + TPageParam + > | DefaultedQueryObserverOptions< TQueryFnData, TError, diff --git a/packages/query-core/src/tests/infiniteQueryBehavior.test.tsx b/packages/query-core/src/tests/infiniteQueryBehavior.test.tsx index c0719b08fb..86ec02c485 100644 --- a/packages/query-core/src/tests/infiniteQueryBehavior.test.tsx +++ b/packages/query-core/src/tests/infiniteQueryBehavior.test.tsx @@ -24,6 +24,8 @@ describe('InfiniteQueryBehavior', () => { const observer = new InfiniteQueryObserver(queryClient, { queryKey: key, retry: false, + defaultPageParam: 1, + getNextPageParam: () => 2, }) let observerResult: @@ -48,12 +50,10 @@ describe('InfiniteQueryBehavior', () => { const key = queryKey() let abortSignal: AbortSignal | null = null - const queryFnSpy = jest - .fn() - .mockImplementation(({ pageParam = 1, signal }) => { - abortSignal = signal - return pageParam - }) + const queryFnSpy = jest.fn().mockImplementation(({ pageParam, signal }) => { + abortSignal = signal + return pageParam + }) const observer = new InfiniteQueryObserver(queryClient, { queryKey: key, @@ -61,6 +61,7 @@ describe('InfiniteQueryBehavior', () => { getNextPageParam: (lastPage) => lastPage + 1, getPreviousPageParam: (firstPage) => firstPage - 1, maxPages: 2, + defaultPageParam: 1, }) let observerResult: @@ -75,13 +76,13 @@ describe('InfiniteQueryBehavior', () => { await waitFor(() => expect(observerResult).toMatchObject({ isFetching: false, - data: { pages: [1], pageParams: [undefined] }, + data: { pages: [1], pageParams: [1] }, }), ) expect(queryFnSpy).toHaveBeenNthCalledWith(1, { queryKey: key, - pageParam: undefined, + pageParam: 1, meta: undefined, signal: abortSignal, }) @@ -100,7 +101,7 @@ describe('InfiniteQueryBehavior', () => { expect(observerResult).toMatchObject({ isFetching: false, - data: { pages: [1, 2], pageParams: [undefined, 2] }, + data: { pages: [1, 2], pageParams: [1, 2] }, }) queryFnSpy.mockClear() @@ -118,7 +119,7 @@ describe('InfiniteQueryBehavior', () => { // Only first two pages should be in the data expect(observerResult).toMatchObject({ isFetching: false, - data: { pages: [0, 1], pageParams: [0, undefined] }, + data: { pages: [0, 1], pageParams: [0, 1] }, }) queryFnSpy.mockClear() @@ -180,118 +181,22 @@ describe('InfiniteQueryBehavior', () => { unsubscribe() }) - test('InfiniteQueryBehavior should apply pageParam', async () => { + test('InfiniteQueryBehavior should support query cancellation', 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, + sleep(10) + return pageParam }) - unsubscribe() - }) - - test('InfiniteQueryBehavior should support query cancellation', async () => { - const key = queryKey() - let abortSignal: AbortSignal | null = null - - const queryFnSpy = jest - .fn() - .mockImplementation(({ pageParam = 1, signal }) => { - abortSignal = signal - sleep(10) - return pageParam - }) - const observer = new InfiniteQueryObserver(queryClient, { queryKey: key, queryFn: queryFnSpy, getNextPageParam: (lastPage) => lastPage + 1, getPreviousPageParam: (firstPage) => firstPage - 1, + defaultPageParam: 1, }) let observerResult: @@ -319,7 +224,7 @@ describe('InfiniteQueryBehavior', () => { expect(queryFnSpy).toHaveBeenNthCalledWith(1, { queryKey: key, - pageParam: undefined, + pageParam: 1, meta: undefined, signal: abortSignal, }) @@ -331,18 +236,17 @@ describe('InfiniteQueryBehavior', () => { const key = queryKey() let abortSignal: AbortSignal | null = null - let queryFnSpy = jest - .fn() - .mockImplementation(({ pageParam = 1, signal }) => { - abortSignal = signal - return pageParam - }) + let queryFnSpy = jest.fn().mockImplementation(({ pageParam, signal }) => { + abortSignal = signal + return pageParam + }) const observer = new InfiniteQueryObserver(queryClient, { queryKey: key, queryFn: queryFnSpy, getNextPageParam: (lastPage) => lastPage + 1, getPreviousPageParam: (firstPage) => firstPage - 1, + defaultPageParam: 1, }) let observerResult: @@ -357,7 +261,7 @@ describe('InfiniteQueryBehavior', () => { await waitFor(() => expect(observerResult).toMatchObject({ isFetching: false, - data: { pages: [1], pageParams: [undefined] }, + data: { pages: [1], pageParams: [1] }, }), ) @@ -368,7 +272,7 @@ describe('InfiniteQueryBehavior', () => { expect(observerResult).toMatchObject({ isFetching: false, - data: { pages: [1, 2], pageParams: [undefined, 2] }, + data: { pages: [1, 2], pageParams: [1, 2] }, }) expect(queryFnSpy).toHaveBeenCalledTimes(1) @@ -401,7 +305,7 @@ describe('InfiniteQueryBehavior', () => { isFetching: false, isError: true, error: new CancelledError(), - data: { pages: [1, 2], pageParams: [undefined, 2] }, + data: { pages: [1, 2], pageParams: [1, 2] }, }) // Pages should not have been fetched @@ -409,60 +313,4 @@ describe('InfiniteQueryBehavior', () => { unsubscribe() }) - - test('InfiniteQueryBehavior should return the current pages if no page param can be determined', async () => { - const key = queryKey() - let abortSignal: AbortSignal | null = null - - const queryFnSpy = jest - .fn() - .mockImplementation(({ pageParam = 1, signal }) => { - abortSignal = signal - return pageParam - }) - - 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] }, - }), - ) - - expect(queryFnSpy).toHaveBeenNthCalledWith(1, { - queryKey: key, - pageParam: undefined, - meta: undefined, - signal: abortSignal, - }) - - queryFnSpy.mockClear() - - // Try to fetch a page without page param and getNextPageParam defined - await observer.fetchNextPage() - - // Current pages should be returned - expect(observerResult).toMatchObject({ - isFetching: false, - data: { pages: [1], pageParams: [undefined] }, - }) - - // queryFnSpy should not be called - expect(queryFnSpy).toBeCalledTimes(0) - - unsubscribe() - }) }) diff --git a/packages/query-core/src/tests/infiniteQueryObserver.test.tsx b/packages/query-core/src/tests/infiniteQueryObserver.test.tsx index ed3485ab0b..302183af1a 100644 --- a/packages/query-core/src/tests/infiniteQueryObserver.test.tsx +++ b/packages/query-core/src/tests/infiniteQueryObserver.test.tsx @@ -23,6 +23,8 @@ describe('InfiniteQueryObserver', () => { pages: data.pages.map((x) => `${x}`), pageParams: data.pageParams, }), + defaultPageParam: 1, + getNextPageParam: () => 2, }) let observerResult const unsubscribe = observer.subscribe((result) => { @@ -31,7 +33,7 @@ describe('InfiniteQueryObserver', () => { await sleep(1) unsubscribe() expect(observerResult).toMatchObject({ - data: { pages: ['1'], pageParams: [undefined] }, + data: { pages: ['1'], pageParams: [1] }, }) }) @@ -50,6 +52,8 @@ describe('InfiniteQueryObserver', () => { pages: data.pages.map((x) => `${x}`), pageParams: data.pageParams, }), + defaultPageParam: 1, + getNextPageParam: () => 2, }) let observerResult const unsubscribe = observer.subscribe((result) => { @@ -58,7 +62,7 @@ describe('InfiniteQueryObserver', () => { await sleep(1) unsubscribe() expect(observerResult).toMatchObject({ - data: { pages: ['1'], pageParams: [undefined] }, + data: { pages: ['1'], pageParams: [1] }, }) expect(queryFn).toBeCalledWith(expect.objectContaining({ meta })) }) diff --git a/packages/query-core/src/tests/query.test.tsx b/packages/query-core/src/tests/query.test.tsx index 002c4f07d3..85a2d6b990 100644 --- a/packages/query-core/src/tests/query.test.tsx +++ b/packages/query-core/src/tests/query.test.tsx @@ -200,6 +200,7 @@ describe('query', () => { expect(queryFn).toHaveBeenCalledTimes(1) const args = queryFn.mock.calls[0]![0] expect(args).toBeDefined() + // @ts-expect-error page param should be undefined expect(args.pageParam).toBeUndefined() expect(args.queryKey).toEqual(key) expect(args.signal).toBeInstanceOf(AbortSignal) diff --git a/packages/query-core/src/tests/queryClient.test.tsx b/packages/query-core/src/tests/queryClient.test.tsx index 9398438f25..87a0c4aa54 100644 --- a/packages/query-core/src/tests/queryClient.test.tsx +++ b/packages/query-core/src/tests/queryClient.test.tsx @@ -562,10 +562,10 @@ describe('queryClient', () => { const data = { pages: ['data'], - pageParams: [undefined], + pageParams: [0], } as const - const fetchFn: QueryFunction = () => + const fetchFn: QueryFunction = () => Promise.resolve(data.pages[0]) await expect( @@ -573,8 +573,9 @@ describe('queryClient', () => { StrictData, any, StrictData, - StrictQueryKey - >({ queryKey: key, queryFn: fetchFn }), + StrictQueryKey, + number + >({ queryKey: key, queryFn: fetchFn, defaultPageParam: 0 }), ).resolves.toEqual(data) }) @@ -582,13 +583,14 @@ describe('queryClient', () => { const key = queryKey() const result = await queryClient.fetchInfiniteQuery({ queryKey: key, - queryFn: ({ pageParam = 10 }) => Number(pageParam), + defaultPageParam: 10, + queryFn: ({ pageParam }) => Number(pageParam), }) const result2 = queryClient.getQueryData(key) const expected = { pages: [10], - pageParams: [undefined], + pageParams: [10], } expect(result).toEqual(expected) @@ -602,21 +604,22 @@ describe('queryClient', () => { type StrictQueryKey = ['strict', ...ReturnType] const key: StrictQueryKey = ['strict', ...queryKey()] - const fetchFn: QueryFunction = () => + const fetchFn: QueryFunction = () => Promise.resolve('data') await queryClient.prefetchInfiniteQuery< StrictData, any, StrictData, - StrictQueryKey - >({ queryKey: key, queryFn: fetchFn }) + StrictQueryKey, + number + >({ queryKey: key, queryFn: fetchFn, defaultPageParam: 0 }) const result = queryClient.getQueryData(key) expect(result).toEqual({ pages: ['data'], - pageParams: [undefined], + pageParams: [0], }) }) @@ -625,14 +628,15 @@ describe('queryClient', () => { await queryClient.prefetchInfiniteQuery({ queryKey: key, - queryFn: ({ pageParam = 10 }) => Number(pageParam), + queryFn: ({ pageParam }) => Number(pageParam), + defaultPageParam: 10, }) const result = queryClient.getQueryData(key) expect(result).toEqual({ pages: [10], - pageParams: [undefined], + pageParams: [10], }) }) }) diff --git a/packages/query-core/src/types.ts b/packages/query-core/src/types.ts index 11d4300139..a82ea0c6be 100644 --- a/packages/query-core/src/types.ts +++ b/packages/query-core/src/types.ts @@ -22,17 +22,24 @@ export type QueryKey = readonly unknown[] export type QueryFunction< T = unknown, TQueryKey extends QueryKey = QueryKey, -> = (context: QueryFunctionContext) => T | Promise + TPageParam = never, +> = (context: QueryFunctionContext) => T | Promise -export interface QueryFunctionContext< +export type QueryFunctionContext< TQueryKey extends QueryKey = QueryKey, - TPageParam = any, -> { - queryKey: TQueryKey - signal: AbortSignal - pageParam?: TPageParam - meta: QueryMeta | undefined -} + TPageParam = never, +> = [TPageParam] extends [never] + ? { + queryKey: TQueryKey + signal: AbortSignal + meta: QueryMeta | undefined + } + : { + queryKey: TQueryKey + signal: AbortSignal + pageParam: TPageParam + meta: QueryMeta | undefined + } export type InitialDataFunction = () => T | undefined @@ -50,15 +57,15 @@ export type QueryKeyHashFunction = ( queryKey: TQueryKey, ) => string -export type GetPreviousPageParamFunction = ( +export type GetPreviousPageParamFunction = ( firstPage: TQueryFnData, allPages: TQueryFnData[], -) => unknown +) => TPageParam | undefined -export type GetNextPageParamFunction = ( +export type GetNextPageParamFunction = ( lastPage: TQueryFnData, allPages: TQueryFnData[], -) => unknown +) => TPageParam | undefined export interface InfiniteData { pages: TData[] @@ -76,6 +83,7 @@ export interface QueryOptions< TError = RegisteredError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, + TPageParam = never, > { /** * If `false`, failed queries will not retry by default. @@ -87,13 +95,13 @@ export interface QueryOptions< retryDelay?: RetryDelayValue networkMode?: NetworkMode gcTime?: number - queryFn?: QueryFunction + queryFn?: QueryFunction queryHash?: string queryKey?: TQueryKey queryKeyHashFn?: QueryKeyHashFunction initialData?: TData | InitialDataFunction initialDataUpdatedAt?: number | (() => number | undefined) - behavior?: QueryBehavior + behavior?: QueryBehavior /** * Set this to `false` to disable structural sharing between query results. * Set this to a function which accepts the old and new data and returns resolved data of the same type to implement custom structural sharing logic. @@ -102,16 +110,6 @@ export interface QueryOptions< structuralSharing?: | boolean | ((oldData: TData | undefined, newData: TData) => TData) - /** - * This function can be set to automatically get the previous cursor for infinite queries. - * The result will also be used to determine the value of `hasPreviousPage`. - */ - getPreviousPageParam?: GetPreviousPageParamFunction - /** - * This function can be set to automatically get the next cursor for infinite queries. - * The result will also be used to determine the value of `hasNextPage`. - */ - getNextPageParam?: GetNextPageParamFunction _defaulted?: boolean /** * Additional payload to be stored on each query. @@ -124,6 +122,26 @@ export interface QueryOptions< maxPages?: number } +export interface DefaultPageParam { + defaultPageParam: TPageParam +} + +export interface InfiniteQueryPageParamsOptions< + TQueryFnData = unknown, + TPageParam = unknown, +> extends DefaultPageParam { + /** + * This function can be set to automatically get the previous cursor for infinite queries. + * The result will also be used to determine the value of `hasPreviousPage`. + */ + getPreviousPageParam?: GetPreviousPageParamFunction + /** + * This function can be set to automatically get the next cursor for infinite queries. + * The result will also be used to determine the value of `hasNextPage`. + */ + getNextPageParam: GetNextPageParamFunction +} + export type ThrowErrors< TQueryFnData, TError, @@ -142,7 +160,14 @@ export interface QueryObserverOptions< TData = TQueryFnData, TQueryData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, -> extends QueryOptions { + TPageParam = never, +> extends QueryOptions< + TQueryFnData, + TError, + TQueryData, + TQueryKey, + TPageParam + > { /** * Set this to `false` to disable automatic refetching when the query mounts or changes query keys. * To refetch the query, use the `refetch` method returned from the `useQuery` instance. @@ -281,13 +306,16 @@ export interface InfiniteQueryObserverOptions< TData = TQueryFnData, TQueryData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, > extends QueryObserverOptions< - TQueryFnData, - TError, - InfiniteData, - InfiniteData, - TQueryKey - > {} + TQueryFnData, + TError, + TData, + InfiniteData, + TQueryKey, + TPageParam + >, + InfiniteQueryPageParamsOptions {} export type DefaultedInfiniteQueryObserverOptions< TQueryFnData = unknown, @@ -311,8 +339,9 @@ export interface FetchQueryOptions< TError = RegisteredError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, + TPageParam = never, > extends WithRequired< - QueryOptions, + QueryOptions, 'queryKey' > { /** @@ -327,12 +356,15 @@ export interface FetchInfiniteQueryOptions< TError = RegisteredError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, > extends FetchQueryOptions< - TQueryFnData, - TError, - InfiniteData, - TQueryKey - > {} + TQueryFnData, + TError, + InfiniteData, + TQueryKey, + TPageParam + >, + DefaultPageParam {} export interface ResultOptions { throwOnError?: boolean @@ -353,12 +385,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' @@ -471,15 +501,15 @@ export type QueryObserverResult = export interface InfiniteQueryObserverBaseResult< TData = unknown, TError = RegisteredError, -> extends QueryObserverBaseResult, TError> { +> extends QueryObserverBaseResult { fetchNextPage: ( options?: FetchNextPageOptions, ) => Promise> fetchPreviousPage: ( options?: FetchPreviousPageOptions, ) => Promise> - hasNextPage?: boolean - hasPreviousPage?: boolean + hasNextPage: boolean + hasPreviousPage: boolean isFetchingNextPage: boolean isFetchingPreviousPage: boolean } @@ -516,7 +546,7 @@ export interface InfiniteQueryObserverRefetchErrorResult< TData = unknown, TError = RegisteredError, > extends InfiniteQueryObserverBaseResult { - data: InfiniteData + data: TData error: TError isError: true isPending: false @@ -530,7 +560,7 @@ export interface InfiniteQueryObserverSuccessResult< TData = unknown, TError = RegisteredError, > extends InfiniteQueryObserverBaseResult { - data: InfiniteData + data: TData error: null isError: false isPending: false diff --git a/packages/react-query/src/__tests__/ssr.test.tsx b/packages/react-query/src/__tests__/ssr.test.tsx index 7662647735..9d87b4f255 100644 --- a/packages/react-query/src/__tests__/ssr.test.tsx +++ b/packages/react-query/src/__tests__/ssr.test.tsx @@ -131,7 +131,12 @@ describe('Server Side Rendering', () => { }) function Page() { - const query = useInfiniteQuery({ queryKey: key, queryFn }) + const query = useInfiniteQuery({ + queryKey: key, + queryFn, + getNextPageParam: () => undefined, + defaultPageParam: 0, + }) return (
    {query.data?.pages.map((page) => ( @@ -141,7 +146,11 @@ describe('Server Side Rendering', () => { ) } - await queryClient.prefetchInfiniteQuery({ queryKey: key, queryFn }) + await queryClient.prefetchInfiniteQuery({ + queryKey: key, + queryFn, + defaultPageParam: 0, + }) const markup = renderToString( diff --git a/packages/react-query/src/__tests__/suspense.test.tsx b/packages/react-query/src/__tests__/suspense.test.tsx index 8240454ad1..15daf6edec 100644 --- a/packages/react-query/src/__tests__/suspense.test.tsx +++ b/packages/react-query/src/__tests__/suspense.test.tsx @@ -1,7 +1,7 @@ import { fireEvent, waitFor } from '@testing-library/react' import * as React from 'react' import { ErrorBoundary } from 'react-error-boundary' -import type { UseInfiniteQueryResult, UseQueryResult } from '..' +import type { InfiniteData, UseInfiniteQueryResult, UseQueryResult } from '..' import { QueryCache, QueryErrorResetBoundary, @@ -68,17 +68,18 @@ describe("useQuery's in Suspense mode", () => { it('should return the correct states for a successful infinite query', async () => { const key = queryKey() - const states: UseInfiniteQueryResult[] = [] + const states: UseInfiniteQueryResult>[] = [] function Page() { const [multiplier, setMultiplier] = React.useState(1) const state = useInfiniteQuery({ queryKey: [`${key}_${multiplier}`], - queryFn: async ({ pageParam = 1 }) => { + queryFn: async ({ pageParam }) => { await sleep(10) return Number(pageParam * multiplier) }, suspense: true, + defaultPageParam: 1, getNextPageParam: (lastPage) => lastPage + 1, }) states.push(state) @@ -101,7 +102,7 @@ describe("useQuery's in Suspense mode", () => { expect(states.length).toBe(1) expect(states[0]).toMatchObject({ - data: { pages: [1], pageParams: [undefined] }, + data: { pages: [1], pageParams: [1] }, status: 'success', }) @@ -110,7 +111,7 @@ describe("useQuery's in Suspense mode", () => { expect(states.length).toBe(2) expect(states[1]).toMatchObject({ - data: { pages: [2], pageParams: [undefined] }, + data: { pages: [2], pageParams: [1] }, status: 'success', }) }) diff --git a/packages/react-query/src/__tests__/useInfiniteQuery.test.tsx b/packages/react-query/src/__tests__/useInfiniteQuery.test.tsx index cabfc34582..b1ea73a243 100644 --- a/packages/react-query/src/__tests__/useInfiniteQuery.test.tsx +++ b/packages/react-query/src/__tests__/useInfiniteQuery.test.tsx @@ -45,13 +45,14 @@ describe('useInfiniteQuery', () => { it('should return the correct states for a successful query', async () => { const key = queryKey() - const states: UseInfiniteQueryResult[] = [] + const states: UseInfiniteQueryResult>[] = [] function Page() { const state = useInfiniteQuery({ queryKey: key, - queryFn: ({ pageParam = 0 }) => Number(pageParam), + queryFn: ({ pageParam }) => Number(pageParam), getNextPageParam: (lastPage) => lastPage + 1, + defaultPageParam: 0, }) states.push(state) return null @@ -72,8 +73,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, @@ -96,7 +97,7 @@ describe('useInfiniteQuery', () => { }) expect(states[1]).toEqual({ - data: { pages: [0], pageParams: [undefined] }, + data: { pages: [0], pageParams: [0] }, dataUpdatedAt: expect.any(Number), error: null, errorUpdatedAt: 0, @@ -106,7 +107,7 @@ describe('useInfiniteQuery', () => { fetchNextPage: expect.any(Function), fetchPreviousPage: expect.any(Function), hasNextPage: true, - hasPreviousPage: undefined, + hasPreviousPage: false, isError: false, isFetched: true, isFetchedAfterMount: true, @@ -137,7 +138,7 @@ describe('useInfiniteQuery', () => { const start = 1 const state = useInfiniteQuery({ queryKey: key, - queryFn: async ({ pageParam = start }) => { + queryFn: async ({ pageParam }) => { if (pageParam === 2) { throw new Error('error') } @@ -146,6 +147,7 @@ describe('useInfiniteQuery', () => { retry: 1, retryDelay: 10, getNextPageParam: (lastPage) => lastPage + 1, + defaultPageParam: start, }) const { fetchNextPage } = state @@ -170,18 +172,19 @@ describe('useInfiniteQuery', () => { it('should keep the previous data when placeholderData is set', async () => { const key = queryKey() - const states: UseInfiniteQueryResult[] = [] + const states: UseInfiniteQueryResult>[] = [] function Page() { const [order, setOrder] = React.useState('desc') const state = useInfiniteQuery({ queryKey: [key, order], - queryFn: async ({ pageParam = 0 }) => { + queryFn: async ({ pageParam }) => { await sleep(10) return `${pageParam}-${order}` }, getNextPageParam: () => 1, + defaultPageParam: 0, placeholderData: keepPreviousData, notifyOnChangeProps: 'all', }) @@ -265,7 +268,7 @@ describe('useInfiniteQuery', () => { it('should be able to select a part of the data', async () => { const key = queryKey() - const states: UseInfiniteQueryResult[] = [] + const states: UseInfiniteQueryResult>[] = [] function Page() { const state = useInfiniteQuery({ @@ -275,6 +278,8 @@ describe('useInfiniteQuery', () => { pages: data.pages.map((x) => `count: ${x.count}`), pageParams: data.pageParams, }), + getNextPageParam: () => undefined, + defaultPageParam: 0, }) states.push(state) @@ -300,7 +305,9 @@ describe('useInfiniteQuery', () => { it('should be able to select a new result and not cause infinite renders', async () => { const key = queryKey() - const states: UseInfiniteQueryResult<{ count: number; id: number }>[] = [] + const states: UseInfiniteQueryResult< + InfiniteData<{ count: number; id: number }> + >[] = [] let selectCalled = 0 function Page() { @@ -314,6 +321,8 @@ describe('useInfiniteQuery', () => { pageParams: data.pageParams, } }, []), + getNextPageParam: () => undefined, + defaultPageParam: 0, }) states.push(state) @@ -346,12 +355,12 @@ describe('useInfiniteQuery', () => { it('should be able to reverse the data', async () => { const key = queryKey() - const states: UseInfiniteQueryResult[] = [] + const states: UseInfiniteQueryResult>[] = [] function Page() { const state = useInfiniteQuery({ queryKey: key, - queryFn: async ({ pageParam = 0 }) => { + queryFn: async ({ pageParam }) => { await sleep(10) return Number(pageParam) }, @@ -360,15 +369,15 @@ describe('useInfiniteQuery', () => { pageParams: [...data.pageParams].reverse(), }), notifyOnChangeProps: 'all', + getNextPageParam: () => 1, + defaultPageParam: 0, }) states.push(state) return (
    - +
    data: {state.data?.pages.join(',') ?? 'null'}
    isFetching: {state.isFetching}
    @@ -403,16 +412,18 @@ describe('useInfiniteQuery', () => { it('should be able to fetch a previous page', async () => { const key = queryKey() - const states: UseInfiniteQueryResult[] = [] + const states: UseInfiniteQueryResult>[] = [] function Page() { const start = 10 const state = useInfiniteQuery({ queryKey: key, - queryFn: async ({ pageParam = start }) => { + queryFn: async ({ pageParam }) => { await sleep(10) return Number(pageParam) }, + defaultPageParam: start, + getNextPageParam: (lastPage) => lastPage + 1, getPreviousPageParam: (firstPage) => firstPage - 1, notifyOnChangeProps: 'all', }) @@ -446,8 +457,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, @@ -455,7 +466,7 @@ describe('useInfiniteQuery', () => { }) expect(states[1]).toMatchObject({ data: { pages: [10] }, - hasNextPage: undefined, + hasNextPage: true, hasPreviousPage: true, isFetching: false, isFetchingNextPage: false, @@ -464,7 +475,7 @@ describe('useInfiniteQuery', () => { }) expect(states[2]).toMatchObject({ data: { pages: [10] }, - hasNextPage: undefined, + hasNextPage: true, hasPreviousPage: true, isFetching: true, isFetchingNextPage: false, @@ -473,7 +484,7 @@ describe('useInfiniteQuery', () => { }) expect(states[3]).toMatchObject({ data: { pages: [9, 10] }, - hasNextPage: undefined, + hasNextPage: true, hasPreviousPage: true, isFetching: false, isFetchingNextPage: false, @@ -482,125 +493,18 @@ 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[] = [] + const states: UseInfiniteQueryResult>[] = [] function Page() { const state = useInfiniteQuery({ queryKey: key, - queryFn: async ({ pageParam = 10 }) => { + queryFn: async ({ pageParam }) => { await sleep(10) return Number(pageParam) }, - + defaultPageParam: 10, getPreviousPageParam: (firstPage) => firstPage - 1, getNextPageParam: (lastPage) => lastPage + 1, notifyOnChangeProps: 'all', @@ -700,16 +604,17 @@ describe('useInfiniteQuery', () => { it('should silently cancel any ongoing fetch when fetching more', async () => { const key = queryKey() - const states: UseInfiniteQueryResult[] = [] + const states: UseInfiniteQueryResult>[] = [] function Page() { const start = 10 const state = useInfiniteQuery({ queryKey: key, - queryFn: async ({ pageParam = start }) => { + queryFn: async ({ pageParam }) => { await sleep(50) return Number(pageParam) }, + defaultPageParam: start, getNextPageParam: (lastPage) => lastPage + 1, notifyOnChangeProps: 'all', }) @@ -736,7 +641,7 @@ describe('useInfiniteQuery', () => { expect(states.length).toBe(5) expect(states[0]).toMatchObject({ - hasNextPage: undefined, + hasNextPage: false, data: undefined, isFetching: true, isFetchingNextPage: false, @@ -780,7 +685,7 @@ describe('useInfiniteQuery', () => { const fetchPage = jest.fn< Promise, [QueryFunctionContext] - >(async ({ pageParam = start, signal }) => { + >(async ({ pageParam, signal }) => { const onAbort = jest.fn() const abortListener = jest.fn() onAborts.push(onAbort) @@ -795,6 +700,7 @@ describe('useInfiniteQuery', () => { const { fetchNextPage } = useInfiniteQuery({ queryKey: key, queryFn: fetchPage, + defaultPageParam: start, getNextPageParam: (lastPage) => lastPage + 1, }) @@ -821,7 +727,7 @@ describe('useInfiniteQuery', () => { let callIndex = 0 const firstCtx = fetchPage.mock.calls[callIndex]![0] - expect(firstCtx.pageParam).toBeUndefined() + expect(firstCtx.pageParam).toEqual(start) expect(firstCtx.queryKey).toEqual(key) expect(firstCtx.signal).toBeInstanceOf(AbortSignal) expect(firstCtx.signal.aborted).toBe(false) @@ -855,7 +761,7 @@ describe('useInfiniteQuery', () => { const fetchPage = jest.fn< Promise, [QueryFunctionContext] - >(async ({ pageParam = start, signal }) => { + >(async ({ pageParam, signal }) => { const onAbort = jest.fn() const abortListener = jest.fn() onAborts.push(onAbort) @@ -871,6 +777,7 @@ describe('useInfiniteQuery', () => { const { fetchNextPage } = useInfiniteQuery({ queryKey: key, queryFn: fetchPage, + defaultPageParam: start, getNextPageParam: (lastPage) => lastPage + 1, }) @@ -897,7 +804,7 @@ describe('useInfiniteQuery', () => { let callIndex = 0 const firstCtx = fetchPage.mock.calls[callIndex]![0] - expect(firstCtx.pageParam).toBeUndefined() + expect(firstCtx.pageParam).toEqual(start) expect(firstCtx.queryKey).toEqual(key) expect(firstCtx.signal).toBeInstanceOf(AbortSignal) expect(firstCtx.signal.aborted).toBe(false) @@ -916,16 +823,17 @@ describe('useInfiniteQuery', () => { it('should keep fetching first page when not loaded yet and triggering fetch more', async () => { const key = queryKey() - const states: UseInfiniteQueryResult[] = [] + const states: UseInfiniteQueryResult>[] = [] function Page() { const start = 10 const state = useInfiniteQuery({ queryKey: key, - queryFn: async ({ pageParam = start }) => { + queryFn: async ({ pageParam }) => { await sleep(50) return Number(pageParam) }, + defaultPageParam: start, getNextPageParam: (lastPage) => lastPage + 1, notifyOnChangeProps: 'all', }) @@ -949,7 +857,7 @@ describe('useInfiniteQuery', () => { expect(states.length).toBe(2) expect(states[0]).toMatchObject({ - hasNextPage: undefined, + hasNextPage: false, data: undefined, isFetching: true, isFetchingNextPage: false, @@ -973,12 +881,13 @@ describe('useInfiniteQuery', () => { function List() { useInfiniteQuery({ queryKey: key, - queryFn: async ({ pageParam = 0, signal: _ }) => { + queryFn: async ({ pageParam, signal: _ }) => { fetches++ await sleep(50) return Number(pageParam) * 10 }, initialData, + defaultPageParam: 0, getNextPageParam: (_, allPages) => { return allPages.length === 4 ? undefined : allPages.length }, @@ -1011,94 +920,21 @@ 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[] = [] + const states: UseInfiniteQueryResult>[] = [] function Page() { const [firstPage, setFirstPage] = React.useState(0) const state = useInfiniteQuery({ queryKey: key, - queryFn: async ({ pageParam = firstPage }) => { + queryFn: async ({ pageParam }) => { await sleep(10) return Number(pageParam) }, getNextPageParam: (lastPage) => lastPage + 1, + defaultPageParam: firstPage, notifyOnChangeProps: 'all', }) @@ -1126,7 +962,7 @@ describe('useInfiniteQuery', () => { expect(states.length).toBe(5) expect(states[0]).toMatchObject({ - hasNextPage: undefined, + hasNextPage: false, data: undefined, isFetching: true, isFetchingNextPage: false, @@ -1168,7 +1004,7 @@ describe('useInfiniteQuery', () => { it('should only refetch the first page when initialData is provided', async () => { const key = queryKey() - const states: UseInfiniteQueryResult[] = [] + const states: UseInfiniteQueryResult>[] = [] function Page() { const state = useInfiniteQuery({ @@ -1179,6 +1015,7 @@ describe('useInfiniteQuery', () => { }, initialData: { pages: [1], pageParams: [1] }, getNextPageParam: (lastPage) => lastPage + 1, + defaultPageParam: 0, notifyOnChangeProps: 'all', }) @@ -1232,13 +1069,14 @@ describe('useInfiniteQuery', () => { it('should set hasNextPage to false if getNextPageParam returns undefined', async () => { const key = queryKey() - const states: UseInfiniteQueryResult[] = [] + const states: UseInfiniteQueryResult>[] = [] function Page() { const state = useInfiniteQuery({ queryKey: key, - queryFn: ({ pageParam = 1 }) => Number(pageParam), + queryFn: ({ pageParam }) => Number(pageParam), getNextPageParam: () => undefined, + defaultPageParam: 1, }) states.push(state) @@ -1253,7 +1091,7 @@ describe('useInfiniteQuery', () => { expect(states.length).toBe(2) expect(states[0]).toMatchObject({ data: undefined, - hasNextPage: undefined, + hasNextPage: false, isFetching: true, isFetchingNextPage: false, isSuccess: false, @@ -1269,14 +1107,15 @@ describe('useInfiniteQuery', () => { it('should compute hasNextPage correctly using initialData', async () => { const key = queryKey() - const states: UseInfiniteQueryResult[] = [] + const states: UseInfiniteQueryResult>[] = [] function Page() { const state = useInfiniteQuery({ queryKey: key, - queryFn: ({ pageParam = 10 }): number => pageParam, - initialData: { pages: [10], pageParams: [undefined] }, + queryFn: ({ pageParam }): number => pageParam, + initialData: { pages: [10], pageParams: [10] }, getNextPageParam: (lastPage) => (lastPage === 10 ? 11 : undefined), + defaultPageParam: 10, }) states.push(state) @@ -1307,14 +1146,14 @@ describe('useInfiniteQuery', () => { it('should compute hasNextPage correctly for falsy getFetchMore return value using initialData', async () => { const key = queryKey() - const states: UseInfiniteQueryResult[] = [] + const states: UseInfiniteQueryResult>[] = [] function Page() { const state = useInfiniteQuery({ queryKey: key, - queryFn: ({ pageParam = 10 }): number => pageParam, - - initialData: { pages: [10], pageParams: [undefined] }, + queryFn: ({ pageParam }): number => pageParam, + defaultPageParam: 10, + initialData: { pages: [10], pageParams: [10] }, getNextPageParam: () => undefined, }) @@ -1346,17 +1185,18 @@ describe('useInfiniteQuery', () => { it('should not use selected data when computing hasNextPage', async () => { const key = queryKey() - const states: UseInfiniteQueryResult[] = [] + const states: UseInfiniteQueryResult>[] = [] function Page() { const state = useInfiniteQuery({ queryKey: key, - queryFn: ({ pageParam = 1 }) => Number(pageParam), - getNextPageParam: (lastPage) => (lastPage === 1 ? 2 : false), + queryFn: ({ pageParam }) => Number(pageParam), + getNextPageParam: (lastPage) => (lastPage === 1 ? 2 : undefined), select: (data) => ({ pages: data.pages.map((x) => x.toString()), pageParams: data.pageParams, }), + defaultPageParam: 1, }) states.push(state) @@ -1371,7 +1211,7 @@ describe('useInfiniteQuery', () => { expect(states.length).toBe(2) expect(states[0]).toMatchObject({ data: undefined, - hasNextPage: undefined, + hasNextPage: false, isFetching: true, isFetchingNextPage: false, isSuccess: false, @@ -1412,11 +1252,11 @@ describe('useInfiniteQuery', () => { fetchNextPage, hasNextPage, refetch, - } = useInfiniteQuery({ + } = useInfiniteQuery({ queryKey: key, - queryFn: ({ pageParam = 0 }) => + queryFn: ({ pageParam }) => fetchItemsWithLimit(pageParam, fetchCountRef.current++), - + defaultPageParam: 0, getNextPageParam: (lastPage) => lastPage.nextId, }) @@ -1445,7 +1285,7 @@ describe('useInfiniteQuery', () => {
    +
    data: {state.data?.pages.join(',') ?? 'null'}
    isFetching: {state.isFetching}
    @@ -429,18 +437,19 @@ describe('useInfiniteQuery', () => { it('should be able to fetch a previous page', async () => { const key = queryKey() - const states: CreateInfiniteQueryResult[] = [] + const states: CreateInfiniteQueryResult>[] = [] function Page() { const start = 10 const state = createInfiniteQuery(() => ({ queryKey: key, - queryFn: async ({ pageParam = start }) => { + queryFn: async ({ pageParam }) => { await sleep(10) return Number(pageParam) }, - + getNextPageParam: (lastPage) => lastPage + 1, getPreviousPageParam: (firstPage) => firstPage - 1, + defaultPageParam: start, notifyOnChangeProps: 'all', })) @@ -469,8 +478,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, @@ -478,7 +487,7 @@ describe('useInfiniteQuery', () => { }) expect(states[1]).toMatchObject({ data: { pages: [10] }, - hasNextPage: undefined, + hasNextPage: true, hasPreviousPage: true, isFetching: false, isFetchingNextPage: false, @@ -487,7 +496,7 @@ describe('useInfiniteQuery', () => { }) expect(states[2]).toMatchObject({ data: { pages: [10] }, - hasNextPage: undefined, + hasNextPage: true, hasPreviousPage: true, isFetching: true, isFetchingNextPage: false, @@ -496,7 +505,7 @@ describe('useInfiniteQuery', () => { }) expect(states[3]).toMatchObject({ data: { pages: [9, 10] }, - hasNextPage: undefined, + hasNextPage: true, hasPreviousPage: true, isFetching: false, isFetchingNextPage: false, @@ -505,131 +514,21 @@ 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[] = [] + const states: CreateInfiniteQueryResult>[] = [] function Page() { const state = createInfiniteQuery(() => ({ queryKey: key, - queryFn: async ({ pageParam = 10 }) => { + queryFn: async ({ pageParam }) => { await sleep(10) return Number(pageParam) }, getPreviousPageParam: (firstPage) => firstPage - 1, getNextPageParam: (lastPage) => lastPage + 1, + defaultPageParam: 10, notifyOnChangeProps: 'all', })) @@ -731,18 +630,19 @@ describe('useInfiniteQuery', () => { it('should silently cancel any ongoing fetch when fetching more', async () => { const key = queryKey() - const states: CreateInfiniteQueryResult[] = [] + const states: CreateInfiniteQueryResult>[] = [] function Page() { const start = 10 const state = createInfiniteQuery(() => ({ queryKey: key, - queryFn: async ({ pageParam = start }) => { + queryFn: async ({ pageParam }) => { await sleep(50) return Number(pageParam) }, getNextPageParam: (lastPage) => lastPage + 1, + defaultPageParam: start, notifyOnChangeProps: 'all', })) @@ -773,7 +673,7 @@ describe('useInfiniteQuery', () => { expect(states.length).toBe(5) expect(states[0]).toMatchObject({ - hasNextPage: undefined, + hasNextPage: false, data: undefined, isFetching: true, isFetchingNextPage: false, @@ -817,7 +717,7 @@ describe('useInfiniteQuery', () => { const fetchPage = jest.fn< Promise, [QueryFunctionContext] - >(async ({ pageParam = start, signal }) => { + >(async ({ pageParam, signal }) => { const onAbort = jest.fn() const abortListener = jest.fn() onAborts.push(onAbort) @@ -834,6 +734,7 @@ describe('useInfiniteQuery', () => { queryKey: key, queryFn: fetchPage, getNextPageParam: (lastPage) => lastPage + 1, + defaultPageParam: start, })) createEffect(() => { @@ -864,7 +765,7 @@ describe('useInfiniteQuery', () => { let callIndex = 0 const firstCtx = fetchPage.mock.calls[callIndex]![0] - expect(firstCtx.pageParam).toBeUndefined() + expect(firstCtx.pageParam).toEqual(start) expect(firstCtx.queryKey).toEqual(key) expect(firstCtx.signal).toBeInstanceOf(AbortSignal) expect(firstCtx.signal.aborted).toBe(false) @@ -898,7 +799,7 @@ describe('useInfiniteQuery', () => { const fetchPage = jest.fn< Promise, [QueryFunctionContext] - >(async ({ pageParam = start, signal }) => { + >(async ({ pageParam, signal }) => { const onAbort = jest.fn() const abortListener = jest.fn() onAborts.push(onAbort) @@ -915,6 +816,7 @@ describe('useInfiniteQuery', () => { queryKey: key, queryFn: fetchPage, getNextPageParam: (lastPage) => lastPage + 1, + defaultPageParam: start, })) createEffect(() => { @@ -945,7 +847,7 @@ describe('useInfiniteQuery', () => { let callIndex = 0 const firstCtx = fetchPage.mock.calls[callIndex]![0] - expect(firstCtx.pageParam).toBeUndefined() + expect(firstCtx.pageParam).toEqual(start) expect(firstCtx.queryKey).toEqual(key) expect(firstCtx.signal).toBeInstanceOf(AbortSignal) expect(firstCtx.signal.aborted).toBe(false) @@ -964,18 +866,19 @@ describe('useInfiniteQuery', () => { it('should keep fetching first page when not loaded yet and triggering fetch more', async () => { const key = queryKey() - const states: CreateInfiniteQueryResult[] = [] + const states: CreateInfiniteQueryResult>[] = [] function Page() { const start = 10 const state = createInfiniteQuery(() => ({ queryKey: key, - queryFn: async ({ pageParam = start }) => { + queryFn: async ({ pageParam }) => { await sleep(50) return Number(pageParam) }, getNextPageParam: (lastPage) => lastPage + 1, + defaultPageParam: start, notifyOnChangeProps: 'all', })) @@ -1003,7 +906,7 @@ describe('useInfiniteQuery', () => { expect(states.length).toBe(2) expect(states[0]).toMatchObject({ - hasNextPage: undefined, + hasNextPage: false, data: undefined, isFetching: true, isFetchingNextPage: false, @@ -1027,7 +930,7 @@ describe('useInfiniteQuery', () => { function List() { createInfiniteQuery(() => ({ queryKey: key, - queryFn: async ({ pageParam = 0, signal: _ }) => { + queryFn: async ({ pageParam, signal: _ }) => { fetches++ await sleep(50) return Number(pageParam) * 10 @@ -1037,6 +940,7 @@ describe('useInfiniteQuery', () => { getNextPageParam: (_, allPages) => { return allPages.length === 4 ? undefined : allPages.length }, + defaultPageParam: 0, })) return null @@ -1070,91 +974,23 @@ 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[] = [] + const states: CreateInfiniteQueryResult>[] = [] function Page() { const [firstPage, setFirstPage] = createSignal(0) const state = createInfiniteQuery(() => ({ queryKey: key, - queryFn: async ({ pageParam = firstPage() }) => { + queryFn: async ({ pageParam }) => { await sleep(10) return Number(pageParam) }, getNextPageParam: (lastPage) => lastPage + 1, notifyOnChangeProps: 'all', + defaultPageParam: firstPage(), })) createRenderEffect(() => { @@ -1186,7 +1022,7 @@ describe('useInfiniteQuery', () => { expect(states.length).toBe(5) expect(states[0]).toMatchObject({ - hasNextPage: undefined, + hasNextPage: false, data: undefined, isFetching: true, isFetchingNextPage: false, @@ -1228,7 +1064,7 @@ describe('useInfiniteQuery', () => { it('should only refetch the first page when initialData is provided', async () => { const key = queryKey() - const states: CreateInfiniteQueryResult[] = [] + const states: CreateInfiniteQueryResult>[] = [] function Page() { const state = createInfiniteQuery(() => ({ @@ -1240,6 +1076,7 @@ describe('useInfiniteQuery', () => { initialData: { pages: [1], pageParams: [1] }, getNextPageParam: (lastPage) => lastPage + 1, + defaultPageParam: 0, notifyOnChangeProps: 'all', })) @@ -1298,13 +1135,13 @@ describe('useInfiniteQuery', () => { it('should set hasNextPage to false if getNextPageParam returns undefined', async () => { const key = queryKey() - const states: CreateInfiniteQueryResult[] = [] + const states: CreateInfiniteQueryResult>[] = [] function Page() { const state = createInfiniteQuery(() => ({ queryKey: key, - queryFn: ({ pageParam = 1 }) => Number(pageParam), - + queryFn: ({ pageParam }) => Number(pageParam), + defaultPageParam: 1, getNextPageParam: () => undefined, })) @@ -1326,7 +1163,7 @@ describe('useInfiniteQuery', () => { expect(states.length).toBe(2) expect(states[0]).toMatchObject({ data: undefined, - hasNextPage: undefined, + hasNextPage: false, isFetching: true, isFetchingNextPage: false, isSuccess: false, @@ -1342,14 +1179,14 @@ describe('useInfiniteQuery', () => { it('should compute hasNextPage correctly using initialData', async () => { const key = queryKey() - const states: CreateInfiniteQueryResult[] = [] + const states: CreateInfiniteQueryResult>[] = [] function Page() { const state = createInfiniteQuery(() => ({ queryKey: key, - queryFn: ({ pageParam = 10 }): number => pageParam, - - initialData: { pages: [10], pageParams: [undefined] }, + queryFn: ({ pageParam }): number => pageParam, + defaultPageParam: 10, + initialData: { pages: [10], pageParams: [10] }, getNextPageParam: (lastPage) => (lastPage === 10 ? 11 : undefined), })) @@ -1386,14 +1223,14 @@ describe('useInfiniteQuery', () => { it('should compute hasNextPage correctly for falsy getFetchMore return value using initialData', async () => { const key = queryKey() - const states: CreateInfiniteQueryResult[] = [] + const states: CreateInfiniteQueryResult>[] = [] function Page() { const state = createInfiniteQuery(() => ({ queryKey: key, - queryFn: ({ pageParam = 10 }): number => pageParam, - - initialData: { pages: [10], pageParams: [undefined] }, + queryFn: ({ pageParam }): number => pageParam, + defaultPageParam: 10, + initialData: { pages: [10], pageParams: [10] }, getNextPageParam: () => undefined, })) @@ -1430,14 +1267,14 @@ describe('useInfiniteQuery', () => { it('should not use selected data when computing hasNextPage', async () => { const key = queryKey() - const states: CreateInfiniteQueryResult[] = [] + const states: CreateInfiniteQueryResult>[] = [] function Page() { const state = createInfiniteQuery(() => ({ queryKey: key, - queryFn: ({ pageParam = 1 }) => Number(pageParam), - - getNextPageParam: (lastPage) => (lastPage === 1 ? 2 : false), + queryFn: ({ pageParam }) => Number(pageParam), + defaultPageParam: 1, + getNextPageParam: (lastPage) => (lastPage === 1 ? 2 : undefined), select: (data) => ({ pages: data.pages.map((x) => x.toString()), pageParams: data.pageParams, @@ -1462,7 +1299,7 @@ describe('useInfiniteQuery', () => { expect(states.length).toBe(2) expect(states[0]).toMatchObject({ data: undefined, - hasNextPage: undefined, + hasNextPage: false, isFetching: true, isFetchingNextPage: false, isSuccess: false, @@ -1495,11 +1332,11 @@ describe('useInfiniteQuery', () => { function Page() { let fetchCountRef = 0 - const state = createInfiniteQuery(() => ({ + const state = createInfiniteQuery(() => ({ queryKey: key, - queryFn: ({ pageParam = 0 }) => + queryFn: ({ pageParam }) => fetchItemsWithLimit(pageParam, fetchCountRef++), - + defaultPageParam: 0, getNextPageParam: (lastPage) => lastPage.nextId, })) @@ -1625,15 +1462,15 @@ describe('useInfiniteQuery', () => { let fetchCountRef = 0 const [isRemovedLastPage, setIsRemovedLastPage] = createSignal(false) - const state = createInfiniteQuery(() => ({ + const state = createInfiniteQuery(() => ({ queryKey: key, - queryFn: ({ pageParam = 0 }) => + queryFn: ({ pageParam }) => fetchItems( pageParam, fetchCountRef++, pageParam === MAX || (pageParam === MAX - 1 && isRemovedLastPage()), ), - + defaultPageParam: 0, getNextPageParam: (lastPage) => lastPage.nextId, })) @@ -1763,7 +1600,12 @@ describe('useInfiniteQuery', () => { } function Page() { - const state = createInfiniteQuery(() => ({ queryKey: key, queryFn })) + const state = createInfiniteQuery(() => ({ + queryKey: key, + queryFn, + getNextPageParam: () => undefined, + defaultPageParam: 0, + })) return (

    Status: {state.status}

    @@ -1792,7 +1634,12 @@ describe('useInfiniteQuery', () => { function Page() { const state = createInfiniteQuery( - () => ({ queryKey: key, queryFn }), + () => ({ + queryKey: key, + queryFn, + getNextPageParam: () => undefined, + defaultPageParam: 0, + }), () => queryClient, ) return ( diff --git a/packages/solid-query/src/__tests__/suspense.test.tsx b/packages/solid-query/src/__tests__/suspense.test.tsx index 907a20d968..69588d94ca 100644 --- a/packages/solid-query/src/__tests__/suspense.test.tsx +++ b/packages/solid-query/src/__tests__/suspense.test.tsx @@ -8,7 +8,11 @@ import { Show, Suspense, } from 'solid-js' -import type { CreateInfiniteQueryResult, CreateQueryResult } from '..' +import type { + CreateInfiniteQueryResult, + CreateQueryResult, + InfiniteData, +} from '..' import { createInfiniteQuery, createQuery, @@ -79,17 +83,17 @@ describe("useQuery's in Suspense mode", () => { it('should return the correct states for a successful infinite query', async () => { const key = queryKey() - const states: CreateInfiniteQueryResult[] = [] + const states: CreateInfiniteQueryResult>[] = [] function Page() { const [multiplier, setMultiplier] = createSignal(1) const state = createInfiniteQuery(() => ({ queryKey: [`${key}_${multiplier()}`], - queryFn: async ({ pageParam = 1 }) => { + queryFn: async ({ pageParam }) => { await sleep(10) return Number(pageParam * multiplier()) }, - + defaultPageParam: 1, suspense: true, getNextPageParam: (lastPage) => lastPage + 1, })) @@ -120,7 +124,7 @@ describe("useQuery's in Suspense mode", () => { // occurs on read. expect(states.length).toBe(2) expect(states[1]).toMatchObject({ - data: { pages: [1], pageParams: [undefined] }, + data: { pages: [1], pageParams: [1] }, status: 'success', }) @@ -130,7 +134,7 @@ describe("useQuery's in Suspense mode", () => { // TODO(lukemurray): in react this is 2 and in solid it is 4 expect(states.length).toBe(4) expect(states[3]).toMatchObject({ - data: { pages: [2], pageParams: [undefined] }, + data: { pages: [2], pageParams: [1] }, status: 'success', }) }) diff --git a/packages/solid-query/src/createInfiniteQuery.ts b/packages/solid-query/src/createInfiniteQuery.ts index b4ed787c7a..18cc7fe591 100644 --- a/packages/solid-query/src/createInfiniteQuery.ts +++ b/packages/solid-query/src/createInfiniteQuery.ts @@ -3,6 +3,7 @@ import type { QueryKey, QueryClient, RegisteredError, + InfiniteData, } from '@tanstack/query-core' import { InfiniteQueryObserver } from '@tanstack/query-core' import type { @@ -15,10 +16,17 @@ import { createMemo } from 'solid-js' export function createInfiniteQuery< TQueryFnData, TError = RegisteredError, - TData = TQueryFnData, + TData = InfiniteData, TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, >( - options: CreateInfiniteQueryOptions, + options: CreateInfiniteQueryOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + >, queryClient?: () => QueryClient, ): CreateInfiniteQueryResult { return createBaseQuery( diff --git a/packages/solid-query/src/types.ts b/packages/solid-query/src/types.ts index df409a9a6b..bebb757790 100644 --- a/packages/solid-query/src/types.ts +++ b/packages/solid-query/src/types.ts @@ -81,13 +81,15 @@ export interface SolidInfiniteQueryOptions< TData = TQueryFnData, TQueryData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, > extends Omit< InfiniteQueryObserverOptions< TQueryFnData, TError, TData, TQueryData, - TQueryKey + TQueryKey, + TPageParam >, 'queryKey' > { @@ -100,13 +102,15 @@ export type CreateInfiniteQueryOptions< TError = RegisteredError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, > = FunctionedParams< SolidInfiniteQueryOptions< TQueryFnData, TError, TData, TQueryFnData, - TQueryKey + TQueryKey, + TPageParam > > diff --git a/packages/svelte-query/src/createInfiniteQuery.ts b/packages/svelte-query/src/createInfiniteQuery.ts index e78e5d9fe7..2aaca7c84d 100644 --- a/packages/svelte-query/src/createInfiniteQuery.ts +++ b/packages/svelte-query/src/createInfiniteQuery.ts @@ -3,6 +3,7 @@ import type { QueryKey, QueryClient, RegisteredError, + InfiniteData, } from '@tanstack/query-core' import { InfiniteQueryObserver } from '@tanstack/query-core' import type { @@ -14,15 +15,17 @@ import { createBaseQuery } from './createBaseQuery' export function createInfiniteQuery< TQueryFnData, TError = RegisteredError, - TData = TQueryFnData, + TData = InfiniteData, TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, >( options: CreateInfiniteQueryOptions< TQueryFnData, TError, TData, TQueryFnData, - TQueryKey + TQueryKey, + TPageParam >, queryClient?: QueryClient, ): CreateInfiniteQueryResult { diff --git a/packages/svelte-query/src/types.ts b/packages/svelte-query/src/types.ts index af81a5268a..c885a764cb 100644 --- a/packages/svelte-query/src/types.ts +++ b/packages/svelte-query/src/types.ts @@ -56,12 +56,14 @@ export interface CreateInfiniteQueryOptions< TData = TQueryFnData, TQueryData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, > extends InfiniteQueryObserverOptions< TQueryFnData, TError, TData, TQueryData, - TQueryKey + TQueryKey, + TPageParam > {} export type CreateInfiniteQueryResult< diff --git a/packages/vue-query/src/__tests__/queryClient.test.ts b/packages/vue-query/src/__tests__/queryClient.test.ts index 5e4935a365..040439dd7b 100644 --- a/packages/vue-query/src/__tests__/queryClient.test.ts +++ b/packages/vue-query/src/__tests__/queryClient.test.ts @@ -263,9 +263,11 @@ describe('QueryCache', () => { queryClient.fetchInfiniteQuery({ queryKey: queryKeyRef, + defaultPageParam: 0, }) expect(QueryClientOrigin.prototype.fetchInfiniteQuery).toBeCalledWith({ + defaultPageParam: 0, queryKey: queryKeyUnref, }) }) @@ -278,9 +280,11 @@ describe('QueryCache', () => { queryClient.prefetchInfiniteQuery({ queryKey: queryKeyRef, queryFn: fn, + defaultPageParam: 0, }) expect(QueryClientOrigin.prototype.prefetchInfiniteQuery).toBeCalledWith({ + defaultPageParam: 0, queryKey: queryKeyUnref, queryFn: fn, }) diff --git a/packages/vue-query/src/__tests__/test-utils.ts b/packages/vue-query/src/__tests__/test-utils.ts index b9a019de40..e4173192d0 100644 --- a/packages/vue-query/src/__tests__/test-utils.ts +++ b/packages/vue-query/src/__tests__/test-utils.ts @@ -20,7 +20,7 @@ export function getSimpleFetcherWithReturnData(returnData: unknown) { } export function infiniteFetcher({ - pageParam = 0, + pageParam, }: { pageParam?: number }): Promise { diff --git a/packages/vue-query/src/__tests__/useInfiniteQuery.test.ts b/packages/vue-query/src/__tests__/useInfiniteQuery.test.ts index 38a869c921..0b3bc826a3 100644 --- a/packages/vue-query/src/__tests__/useInfiniteQuery.test.ts +++ b/packages/vue-query/src/__tests__/useInfiniteQuery.test.ts @@ -8,6 +8,8 @@ describe('useQuery', () => { const { data, fetchNextPage, status } = useInfiniteQuery({ queryKey: ['infiniteQuery'], queryFn: infiniteFetcher, + defaultPageParam: 0, + getNextPageParam: () => 12, }) expect(data.value).toStrictEqual(undefined) @@ -16,17 +18,17 @@ describe('useQuery', () => { await flushPromises() expect(data.value).toStrictEqual({ - pageParams: [undefined], + pageParams: [0], pages: ['data on page 0'], }) expect(status.value).toStrictEqual('success') - fetchNextPage({ pageParam: 12 }) + fetchNextPage() await flushPromises() expect(data.value).toStrictEqual({ - pageParams: [undefined, 12], + pageParams: [0, 12], pages: ['data on page 0', 'data on page 12'], }) expect(status.value).toStrictEqual('success') diff --git a/packages/vue-query/src/__tests__/useInfiniteQuery.types.test.tsx b/packages/vue-query/src/__tests__/useInfiniteQuery.types.test.tsx index c53b184017..82002a45e2 100644 --- a/packages/vue-query/src/__tests__/useInfiniteQuery.types.test.tsx +++ b/packages/vue-query/src/__tests__/useInfiniteQuery.types.test.tsx @@ -11,6 +11,8 @@ describe('Discriminated union return type', () => { useInfiniteQuery({ queryKey: ['infiniteQuery'], queryFn: simpleFetcher, + getNextPageParam: () => undefined, + defaultPageParam: 0, }), ) @@ -27,6 +29,8 @@ describe('Discriminated union return type', () => { useInfiniteQuery({ queryKey: ['infiniteQuery'], queryFn: simpleFetcher, + getNextPageParam: () => undefined, + defaultPageParam: 0, }), ) @@ -45,6 +49,8 @@ describe('Discriminated union return type', () => { useInfiniteQuery({ queryKey: ['infiniteQuery'], queryFn: simpleFetcher, + getNextPageParam: () => undefined, + defaultPageParam: 0, }), ) @@ -62,6 +68,8 @@ describe('Discriminated union return type', () => { useInfiniteQuery({ queryKey: ['infiniteQuery'], queryFn: simpleFetcher, + getNextPageParam: () => undefined, + defaultPageParam: 0, }), ) @@ -79,6 +87,8 @@ describe('Discriminated union return type', () => { useInfiniteQuery({ queryKey: ['infiniteQuery'], queryFn: simpleFetcher, + getNextPageParam: () => undefined, + defaultPageParam: 0, }), ) diff --git a/packages/vue-query/src/queryClient.ts b/packages/vue-query/src/queryClient.ts index c17d160052..7625ab13fe 100644 --- a/packages/vue-query/src/queryClient.ts +++ b/packages/vue-query/src/queryClient.ts @@ -176,17 +176,31 @@ export class QueryClient extends QC { TError = RegisteredError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, >( - options: FetchInfiniteQueryOptions, + options: FetchInfiniteQueryOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + >, ): Promise> fetchInfiniteQuery< TQueryFnData, TError = RegisteredError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, >( options: MaybeRefDeep< - FetchInfiniteQueryOptions + FetchInfiniteQueryOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + > >, ): Promise> { return super.fetchInfiniteQuery(cloneDeepUnref(options)) @@ -197,17 +211,31 @@ export class QueryClient extends QC { TError = RegisteredError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, >( - options: FetchInfiniteQueryOptions, + options: FetchInfiniteQueryOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + >, ): Promise prefetchInfiniteQuery< TQueryFnData, TError = RegisteredError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, >( options: MaybeRefDeep< - FetchInfiniteQueryOptions + FetchInfiniteQueryOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + > >, ): Promise { return super.prefetchInfiniteQuery(cloneDeepUnref(options)) diff --git a/packages/vue-query/src/useBaseQuery.ts b/packages/vue-query/src/useBaseQuery.ts index f03a206abe..ffc9304be1 100644 --- a/packages/vue-query/src/useBaseQuery.ts +++ b/packages/vue-query/src/useBaseQuery.ts @@ -32,19 +32,37 @@ type UseQueryOptionsGeneric< TQueryFnData, TError, TData, + TQueryData, TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, > = - | UseQueryOptions - | UseInfiniteQueryOptions + | UseQueryOptions + | UseInfiniteQueryOptions< + TQueryFnData, + TError, + TData, + TQueryData, + TQueryKey, + TPageParam + > export function useBaseQuery< TQueryFnData, TError, TData, + TQueryData, TQueryKey extends QueryKey, + TPageParam, >( Observer: typeof QueryObserver, - options: UseQueryOptionsGeneric, + options: UseQueryOptionsGeneric< + TQueryFnData, + TError, + TData, + TQueryData, + TQueryKey, + TPageParam + >, queryClient?: QueryClient, ): UseBaseQueryReturnType { const client = queryClient || useQueryClient() @@ -56,7 +74,7 @@ export function useBaseQuery< TQueryFnData, TError, TData, - TQueryFnData, + TQueryData, TQueryKey > diff --git a/packages/vue-query/src/useInfiniteQuery.ts b/packages/vue-query/src/useInfiniteQuery.ts index 1ffa0f100b..72989ab93a 100644 --- a/packages/vue-query/src/useInfiniteQuery.ts +++ b/packages/vue-query/src/useInfiniteQuery.ts @@ -1,5 +1,6 @@ import { InfiniteQueryObserver } from '@tanstack/query-core' import type { + InfiniteData, QueryObserver, WithRequired, QueryKey, @@ -18,23 +19,26 @@ import type { UnwrapRef } from 'vue-demi' export type UseInfiniteQueryOptions< TQueryFnData = unknown, TError = RegisteredError, - TData = unknown, - TQueryData = unknown, + TData = TQueryFnData, + TQueryData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, > = { [Property in keyof InfiniteQueryObserverOptions< TQueryFnData, TError, TData, TQueryData, - TQueryKey + TQueryKey, + TPageParam >]: Property extends 'queryFn' ? InfiniteQueryObserverOptions< TQueryFnData, TError, TData, TQueryData, - UnwrapRef + UnwrapRef, + TPageParam >[Property] : MaybeRefDeep< WithRequired< @@ -43,7 +47,8 @@ export type UseInfiniteQueryOptions< TError, TData, TQueryData, - TQueryKey + TQueryKey, + TPageParam >, 'queryKey' >[Property] @@ -70,10 +75,18 @@ export type UseInfiniteQueryReturnType = DistributiveOmit< export function useInfiniteQuery< TQueryFnData, TError = RegisteredError, - TData = TQueryFnData, + TData = InfiniteData, TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, >( - options: UseInfiniteQueryOptions, + options: UseInfiniteQueryOptions< + TQueryFnData, + TError, + TData, + TQueryFnData, + TQueryKey, + TPageParam + >, queryClient?: QueryClient, ): UseInfiniteQueryReturnType { const result = useBaseQuery( diff --git a/packages/vue-query/src/useQuery.ts b/packages/vue-query/src/useQuery.ts index 97dfd7bb84..b01e075205 100644 --- a/packages/vue-query/src/useQuery.ts +++ b/packages/vue-query/src/useQuery.ts @@ -105,7 +105,13 @@ export function useQuery< TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, >( - options: UseQueryOptions, + options: UseQueryOptions< + TQueryFnData, + TError, + TData, + TQueryFnData, + TQueryKey + >, queryClient?: QueryClient, ): | UseQueryReturnType