diff --git a/.changeset/famous-owls-battle.md b/.changeset/famous-owls-battle.md new file mode 100644 index 00000000000..a3bf1106392 --- /dev/null +++ b/.changeset/famous-owls-battle.md @@ -0,0 +1,9 @@ +--- +'@tanstack/query-core': minor +'@tanstack/solid-query': minor +'@tanstack/vue-query': minor +'@tanstack/preact-query': minor +'@tanstack/react-query': minor +--- + +add query() and infiniteQuery() imperative methods to QueryClient diff --git a/docs/eslint/stable-query-client.md b/docs/eslint/stable-query-client.md index d100382c364..bf4e05efd91 100644 --- a/docs/eslint/stable-query-client.md +++ b/docs/eslint/stable-query-client.md @@ -51,7 +51,7 @@ function App() { ```tsx async function App() { const queryClient = new QueryClient() - await queryClient.prefetchQuery(options) + await queryClient.query(options) } ``` diff --git a/docs/framework/angular/guides/paginated-queries.md b/docs/framework/angular/guides/paginated-queries.md index 0510cfed253..5c366c258c4 100644 --- a/docs/framework/angular/guides/paginated-queries.md +++ b/docs/framework/angular/guides/paginated-queries.md @@ -83,10 +83,12 @@ export class PaginationExampleComponent { effect(() => { // Prefetch the next page! if (!this.query.isPlaceholderData() && this.query.data()?.hasMore) { - this.#queryClient.prefetchQuery({ - queryKey: ['projects', this.page() + 1], - queryFn: () => lastValueFrom(fetchProjects(this.page() + 1)), - }) + void this.#queryClient + .query({ + queryKey: ['projects', this.page() + 1], + queryFn: () => lastValueFrom(fetchProjects(this.page() + 1)), + }) + .catch(noop) } }) } diff --git a/docs/framework/angular/guides/query-options.md b/docs/framework/angular/guides/query-options.md index d63753bbfc9..ce0e1c078bf 100644 --- a/docs/framework/angular/guides/query-options.md +++ b/docs/framework/angular/guides/query-options.md @@ -7,7 +7,7 @@ ref: docs/framework/react/guides/query-options.md [//]: # 'Example1' ```ts -import { queryOptions } from '@tanstack/angular-query-experimental' +import { queryOptions, noop } from '@tanstack/angular-query-experimental' @Injectable({ providedIn: 'root', @@ -38,7 +38,7 @@ queries = inject(QueriesService) postQuery = injectQuery(() => this.queries.post(this.postId())) -queryClient.prefetchQuery(this.queries.post(23)) +queryClient.query(this.queries.post(23)).catch(noop) queryClient.setQueryData(this.queries.post(42).queryKey, newPost) ``` diff --git a/docs/framework/angular/typescript.md b/docs/framework/angular/typescript.md index de3fd49758e..99781ffa50c 100644 --- a/docs/framework/angular/typescript.md +++ b/docs/framework/angular/typescript.md @@ -174,9 +174,11 @@ computed(() => { ## Typing Query Options -If you inline query options into `injectQuery`, you'll get automatic type inference. However, you might want to extract the query options into a separate function to share them between `injectQuery` and e.g. `prefetchQuery` or manage them in a service. In that case, you'd lose type inference. To get it back, you can use the `queryOptions` helper: +If you inline query options into `injectQuery`, you'll get automatic type inference. However, you might want to extract the query options into a separate function to share them between `injectQuery` and e.g. `queryClient.query`, or manage them in a service. In that case, you'd lose type inference. To get it back, you can use the `queryOptions` helper: ```ts +import { noop } from '@tanstack/angular-query-experimental' + @Injectable({ providedIn: 'root', }) @@ -215,7 +217,7 @@ export class Component { postQuery = injectQuery(this.optionsSignal) someMethod() { - this.queryClient.prefetchQuery(this.queries.post(23)) + void this.queryClient.query(this.queries.post(23)).catch(noop) } } ``` diff --git a/docs/framework/preact/reference/functions/usePrefetchQuery.md b/docs/framework/preact/reference/functions/usePrefetchQuery.md index dba7adcec5b..c5aaacd0ad3 100644 --- a/docs/framework/preact/reference/functions/usePrefetchQuery.md +++ b/docs/framework/preact/reference/functions/usePrefetchQuery.md @@ -33,7 +33,7 @@ Defined in: [preact-query/src/usePrefetchQuery.tsx:5](https://github.com/theVeda ### options -[`UsePrefetchQueryOptions`](../interfaces/UsePrefetchQueryOptions.md)\<`TQueryFnData`, `TError`, `TData`, `TQueryKey`\> +`QueryExecuteOptions`\<`TQueryFnData`, `TError`, `TData`, `TQueryKey`\> ### queryClient? diff --git a/docs/framework/preact/reference/index.md b/docs/framework/preact/reference/index.md index cac27f1e440..0d453bb90dd 100644 --- a/docs/framework/preact/reference/index.md +++ b/docs/framework/preact/reference/index.md @@ -12,7 +12,6 @@ title: "@tanstack/preact-query" - [UseBaseQueryOptions](interfaces/UseBaseQueryOptions.md) - [UseInfiniteQueryOptions](interfaces/UseInfiniteQueryOptions.md) - [UseMutationOptions](interfaces/UseMutationOptions.md) -- [UsePrefetchQueryOptions](interfaces/UsePrefetchQueryOptions.md) - [UseQueryOptions](interfaces/UseQueryOptions.md) - [UseSuspenseInfiniteQueryOptions](interfaces/UseSuspenseInfiniteQueryOptions.md) - [UseSuspenseQueryOptions](interfaces/UseSuspenseQueryOptions.md) diff --git a/docs/framework/preact/reference/interfaces/UsePrefetchQueryOptions.md b/docs/framework/preact/reference/interfaces/UsePrefetchQueryOptions.md deleted file mode 100644 index 0bbb4a70307..00000000000 --- a/docs/framework/preact/reference/interfaces/UsePrefetchQueryOptions.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -id: UsePrefetchQueryOptions -title: UsePrefetchQueryOptions ---- - -# Interface: UsePrefetchQueryOptions\ - -Defined in: [preact-query/src/types.ts:49](https://github.com/theVedanta/query/blob/main/packages/preact-query/src/types.ts#L49) - -## Extends - -- `OmitKeyof`\<`FetchQueryOptions`\<`TQueryFnData`, `TError`, `TData`, `TQueryKey`\>, `"queryFn"`\> - -## Type Parameters - -### TQueryFnData - -`TQueryFnData` = `unknown` - -### TError - -`TError` = `DefaultError` - -### TData - -`TData` = `TQueryFnData` - -### TQueryKey - -`TQueryKey` *extends* `QueryKey` = `QueryKey` - -## Properties - -### queryFn? - -```ts -optional queryFn: QueryFunction; -``` - -Defined in: [preact-query/src/types.ts:58](https://github.com/theVedanta/query/blob/main/packages/preact-query/src/types.ts#L58) diff --git a/docs/framework/react/guides/advanced-ssr.md b/docs/framework/react/guides/advanced-ssr.md index 3e1fdedff21..cde7e0473dc 100644 --- a/docs/framework/react/guides/advanced-ssr.md +++ b/docs/framework/react/guides/advanced-ssr.md @@ -116,10 +116,12 @@ import { export async function getStaticProps() { const queryClient = new QueryClient() - await queryClient.prefetchQuery({ - queryKey: ['posts'], - queryFn: getPosts, - }) + await queryClient + .query({ + queryKey: ['posts'], + queryFn: getPosts, + }) + .catch(noop) return { props: { @@ -172,10 +174,12 @@ import Posts from './posts' export default async function PostsPage() { const queryClient = new QueryClient() - await queryClient.prefetchQuery({ - queryKey: ['posts'], - queryFn: getPosts, - }) + await queryClient + .query({ + queryKey: ['posts'], + queryFn: getPosts, + }) + .catch(noop) return ( // Neat! Serialization is now as easy as passing props. @@ -237,10 +241,12 @@ import CommentsServerComponent from './comments-server' export default async function PostsPage() { const queryClient = new QueryClient() - await queryClient.prefetchQuery({ - queryKey: ['posts'], - queryFn: getPosts, - }) + await queryClient + .query({ + queryKey: ['posts'], + queryFn: getPosts, + }) + .catch(noop) return ( @@ -261,10 +267,12 @@ import Comments from './comments' export default async function CommentsServerComponent() { const queryClient = new QueryClient() - await queryClient.prefetchQuery({ - queryKey: ['posts-comments'], - queryFn: getComments, - }) + await queryClient + .query({ + queryKey: ['posts-comments'], + queryFn: getComments, + }) + .catch(noop) return ( @@ -325,8 +333,8 @@ import Posts from './posts' export default async function PostsPage() { const queryClient = new QueryClient() - // Note we are now using fetchQuery() - const posts = await queryClient.fetchQuery({ + // Note we are getting the result from query + const posts = await queryClient.query({ queryKey: ['posts'], queryFn: getPosts, }) @@ -355,7 +363,7 @@ Using React Query with Server Components makes most sense if: It's hard to give general advice on when it makes sense to pair React Query with Server Components and not. **If you are just starting out with a new Server Components app, we suggest you start out with any tools for data fetching your framework provides you with and avoid bringing in React Query until you actually need it.** This might be never, and that's fine, use the right tool for the job! -If you do use it, a good rule of thumb is to avoid `queryClient.fetchQuery` unless you need to catch errors. If you do use it, don't render its result on the server or pass the result to another component, even a Client Component one. +If you do use it, a good rule of thumb is to avoid rendering the result of `queryClient.query` on the server or passing it to another component, even a Client Component one. From the React Query perspective, treat Server Components as a place to prefetch data, nothing more. @@ -424,7 +432,7 @@ export function getQueryClient() { > Note: This works in NextJs and Server Components because React can serialize Promises over the wire when you pass them down to Client Components. -Then, all we need to do is provide a `HydrationBoundary`, but we don't need to `await` prefetches anymore: +Then, all we need to do is provide a `HydrationBoundary`, but we don't need to `await` these prefetches anymore: ```tsx // app/posts/page.tsx @@ -437,10 +445,12 @@ export default function PostsPage() { const queryClient = getQueryClient() // look ma, no await - queryClient.prefetchQuery({ - queryKey: ['posts'], - queryFn: getPosts, - }) + void queryClient + .query({ + queryKey: ['posts'], + queryFn: getPosts, + }) + .catch(noop) return ( @@ -504,10 +514,12 @@ export default function PostsPage() { const queryClient = getQueryClient() // look ma, no await - queryClient.prefetchQuery({ - queryKey: ['posts'], - queryFn: () => getPosts().then(serialize), // <-- serialize the data on the server - }) + void queryClient + .query({ + queryKey: ['posts'], + queryFn: () => getPosts().then(serialize), // <-- serialize the data on the server + }) + .catch(noop) return ( diff --git a/docs/framework/react/guides/initial-query-data.md b/docs/framework/react/guides/initial-query-data.md index 971d05af8f8..20ae385e62b 100644 --- a/docs/framework/react/guides/initial-query-data.md +++ b/docs/framework/react/guides/initial-query-data.md @@ -8,7 +8,7 @@ There are many ways to supply initial data for a query to the cache before you n - Declaratively: - Provide `initialData` to a query to prepopulate its cache if empty - Imperatively: - - [Prefetch the data using `queryClient.prefetchQuery`](./prefetching.md) + - [Prefetch the data using `queryClient.query`](./prefetching.md) - [Manually place the data into the cache using `queryClient.setQueryData`](./prefetching.md) ## Using `initialData` to prepopulate a query @@ -84,7 +84,7 @@ By default, `initialData` is treated as totally fresh, as if it were just fetche This option allows the staleTime to be used for its original purpose, determining how fresh the data needs to be, while also allowing the data to be refetched on mount if the `initialData` is older than the `staleTime`. In the example above, our data needs to be fresh within 1 minute, and we can hint to the query when the initialData was last updated so the query can decide for itself whether the data needs to be refetched again or not. - > If you would rather treat your data as **prefetched data**, we recommend that you use the `prefetchQuery` or `fetchQuery` APIs to populate the cache beforehand, thus letting you configure your `staleTime` independently from your initialData + > If you would rather treat your data as **prefetched data**, we recommend that you use the `query` api to populate the cache beforehand, thus letting you configure your `staleTime` independently from your `initialData`. ### Initial Data Function diff --git a/docs/framework/react/guides/migrating-to-v5.md b/docs/framework/react/guides/migrating-to-v5.md index 058a52ccb1b..db2c764ed8b 100644 --- a/docs/framework/react/guides/migrating-to-v5.md +++ b/docs/framework/react/guides/migrating-to-v5.md @@ -29,8 +29,6 @@ useIsMutating({ mutationKey, ...filters }) // [!code ++] ```tsx queryClient.isFetching(key, filters) // [!code --] queryClient.isFetching({ queryKey, ...filters }) // [!code ++] -queryClient.ensureQueryData(key, filters) // [!code --] -queryClient.ensureQueryData({ queryKey, ...filters }) // [!code ++] queryClient.getQueriesData(key, filters) // [!code --] queryClient.getQueriesData({ queryKey, ...filters }) // [!code ++] queryClient.setQueriesData(key, updater, filters, options) // [!code --] @@ -45,14 +43,6 @@ queryClient.invalidateQueries(key, filters, options) // [!code --] queryClient.invalidateQueries({ queryKey, ...filters }, options) // [!code ++] queryClient.refetchQueries(key, filters, options) // [!code --] queryClient.refetchQueries({ queryKey, ...filters }, options) // [!code ++] -queryClient.fetchQuery(key, fn, options) // [!code --] -queryClient.fetchQuery({ queryKey, queryFn, ...options }) // [!code ++] -queryClient.prefetchQuery(key, fn, options) // [!code --] -queryClient.prefetchQuery({ queryKey, queryFn, ...options }) // [!code ++] -queryClient.fetchInfiniteQuery(key, fn, options) // [!code --] -queryClient.fetchInfiniteQuery({ queryKey, queryFn, ...options }) // [!code ++] -queryClient.prefetchInfiniteQuery(key, fn, options) // [!code --] -queryClient.prefetchInfiniteQuery({ queryKey, queryFn, ...options }) // [!code ++] ``` ```tsx @@ -62,6 +52,39 @@ queryCache.findAll(key, filters) // [!code --] queryCache.findAll({ queryKey, ...filters }) // [!code ++] ``` +### Imperative QueryClient methods + +These methods are deprecated as of Tanstack Query `INSERT_FUTURE_V5_MINOR` and will be removed in v6. + +If you are coming from v4 or earlier: + +```tsx +queryClient.fetchQuery(key, fn, options) // [!code --] +queryClient.query({ queryKey: key, queryFn: fn, ...options }) // [!code ++] +queryClient.fetchInfiniteQuery(key, fn, options) // [!code --] +queryClient.infiniteQuery({ + queryKey: key, + queryFn: fn, + ...options, +}) // [!code ++] + +queryClient.prefetchQuery(key, fn, options) // [!code --] +queryClient.query({ queryKey: key, queryFn: fn, ...options }).catch(noop) // [!code ++] + +queryClient.prefetchInfiniteQuery(key, fn, options) // [!code --] +queryClient + .infiniteQuery({ queryKey: key, queryFn: fn, ...options }) + .catch(noop) // [!code ++] + +queryClient.ensureQueryData(key, options) // [!code --] +queryClient.query({ queryKey: key, ...options, staleTime: 'static' }) // [!code ++] + +queryClient.ensureInfiniteQueryData(key, options) // [!code --] +queryClient.infiniteQuery({ queryKey: key, ...options, staleTime: 'static' }) // [!code ++] +``` + +If you are updating older v5 code, It will be the same as the above except for keeping the single options object + ### `queryClient.getQueryData` now accepts queryKey only as an Argument `queryClient.getQueryData` argument is changed to accept only a `queryKey` diff --git a/docs/framework/react/guides/prefetching.md b/docs/framework/react/guides/prefetching.md index c5dde014681..0ee3e6e1b49 100644 --- a/docs/framework/react/guides/prefetching.md +++ b/docs/framework/react/guides/prefetching.md @@ -16,49 +16,65 @@ In this guide, we'll take a look at the first three, while the fourth will be co One specific use of prefetching is to avoid Request Waterfalls, for an in-depth background and explanation of those, see the [Performance & Request Waterfalls guide](./request-waterfalls.md). -## prefetchQuery & prefetchInfiniteQuery +## Using `query` to prefetch -Before jumping into the different specific prefetch patterns, let's look at the `prefetchQuery` and `prefetchInfiniteQuery` functions. First a few basics: +> [!NOTE] +> These tips replace the use of the now deprecated `prefetchQuery` and `ensureQueryData` methods. If you used an earlier version of this guide, note that those methods will be removed in the next major version of TanStack Query -- Out of the box, these functions use the default `staleTime` configured for the `queryClient` to determine whether existing data in the cache is fresh or needs to be fetched again -- You can also pass a specific `staleTime` like this: `prefetchQuery({ queryKey: ['todos'], queryFn: fn, staleTime: 5000 })` - - This `staleTime` is only used for the prefetch, you still need to set it for any `useQuery` call as well - - If you want to ignore `staleTime` and instead always return data if it's available in the cache, you can use the `ensureQueryData` function. +Prefetching a query uses the `query` method. This method by default will + +- Run the query function +- Cache the result +- Return the result of that query +- Throw if it hits any errors + +For prefetching you often want to modify these defaults: + +- Out of the box, `query` uses the default `staleTime` configured for the `queryClient` to determine whether existing data in the cache is fresh or needs to be fetched again +- You can also pass a specific `staleTime` like this: `query({ queryKey: ['todos'], queryFn: fn, staleTime: 5000 })` + - This `staleTime` is only used for that query fetch, you still need to set it for any `useQuery` call as well + - If you want to always return data if it's available in the cache regardless of the default `staleTime`, you can pass `"static"` in for `staleTime`. - Tip: If you are prefetching on the server, set a default `staleTime` higher than `0` for that `queryClient` to avoid having to pass in a specific `staleTime` to each prefetch call - If no instances of `useQuery` appear for a prefetched query, it will be deleted and garbage collected after the time specified in `gcTime` -- These functions return `Promise` and thus never return query data. If that's something you need, use `fetchQuery`/`fetchInfiniteQuery` instead. -- The prefetch functions never throw errors because they usually try to fetch again in a `useQuery` which is a nice graceful fallback. If you need to catch errors, use `fetchQuery`/`fetchInfiniteQuery` instead. +- If your prefetch is for non-critical data, you can discard the promise with `void` and use `.catch(noop)` to swallow errors. The query will usually try to fetch again in a `useQuery`, which is a nice graceful fallback. -This is how you use `prefetchQuery`: +This is how you use `query` to prefetch: [//]: # 'ExamplePrefetchQuery' ```tsx +import { noop } from '@tanstack/react-query' + const prefetchTodos = async () => { - // The results of this query will be cached like a normal query - await queryClient.prefetchQuery({ - queryKey: ['todos'], - queryFn: fetchTodos, - }) + await queryClient + .query({ + queryKey: ['todos'], + queryFn: fetchTodos, + // Swallow errors here, because usually they will fetch again in `useQuery` + }) + .catch(noop) } ``` [//]: # 'ExamplePrefetchQuery' -Infinite Queries can be prefetched like regular Queries. Per default, only the first page of the Query will be prefetched and will be stored under the given QueryKey. If you want to prefetch more than one page, you can use the `pages` option, in which case you also have to provide a `getNextPageParam` function: +Infinite Queries can be prefetched like regular Queries. By default, only the first page of the Query will be prefetched and will be stored under the given QueryKey. If you want to prefetch more than one page, you can use the `pages` option, in which case you also have to provide a `getNextPageParam` function: [//]: # 'ExamplePrefetchInfiniteQuery' ```tsx -const prefetchProjects = async () => { - // The results of this query will be cached like a normal query - await queryClient.prefetchInfiniteQuery({ - queryKey: ['projects'], - queryFn: fetchProjects, - initialPageParam: 0, - getNextPageParam: (lastPage, pages) => lastPage.nextCursor, - pages: 3, // prefetch the first 3 pages - }) +import { noop } from '@tanstack/react-query' + +const prefetchProjects = () => { + await queryClient + .infiniteQuery({ + queryKey: ['projects'], + queryFn: fetchProjects, + initialPageParam: 0, + getNextPageParam: (lastPage, pages) => lastPage.nextCursor, + pages: 3, // prefetch the first 3 pages + }) + .catch(noop) } ``` @@ -68,7 +84,7 @@ Next, let's look at how you can use these and other ways to prefetch in differen ## Prefetch in event handlers -A straightforward form of prefetching is doing it when the user interacts with something. In this example we'll use `queryClient.prefetchQuery` to start a prefetch on `onMouseEnter` or `onFocus`. +A straightforward form of prefetching is doing it when the user interacts with something. In this example we'll use `queryClient.query` to start a prefetch on `onMouseEnter` or `onFocus`. [//]: # 'ExampleEventHandler' @@ -77,13 +93,13 @@ function ShowDetailsButton() { const queryClient = useQueryClient() const prefetch = () => { - queryClient.prefetchQuery({ + void queryClient.query({ queryKey: ['details'], queryFn: getDetailsData, // Prefetch only fires when data is older than the staleTime, // so in a case like this you definitely want to set one staleTime: 60000, - }) + }).catch(noop) } return ( @@ -224,33 +240,37 @@ function Article({ id }) { } ``` -Another way is to prefetch inside of the query function. This makes sense if you know that every time an article is fetched it's very likely comments will also be needed. For this, we'll use `queryClient.prefetchQuery`: +Another way is to prefetch inside the query function. This makes sense if you know that every time an article is fetched it's very likely comments will also be needed. For this, we'll use `queryClient.query`: ```tsx const queryClient = useQueryClient() const { data: articleData, isPending } = useQuery({ queryKey: ['article', id], queryFn: (...args) => { - queryClient.prefetchQuery({ - queryKey: ['article-comments', id], - queryFn: getArticleCommentsById, - }) + void queryClient + .query({ + queryKey: ['article-comments', id], + queryFn: getArticleCommentsById, + }) + .catch(noop) return getArticleById(...args) }, }) ``` -Prefetching in an effect also works, but note that if you are using `useSuspenseQuery` in the same component, this effect wont run until _after_ the query finishes which might not be what you want. +Prefetching in an effect also works, but note that if you are using `useSuspenseQuery` in the same component, this effect won't run until _after_ the query finishes which might not be what you want. ```tsx const queryClient = useQueryClient() useEffect(() => { - queryClient.prefetchQuery({ - queryKey: ['article-comments', id], - queryFn: getArticleCommentsById, - }) + void queryClient + .query({ + queryKey: ['article-comments', id], + queryFn: getArticleCommentsById, + }) + .catch(noop) }, [queryClient, id]) ``` @@ -273,7 +293,7 @@ Sometimes we want to prefetch conditionally, based on the result of another fetc ```tsx // This lazy loads the GraphFeedItem component, meaning -// it wont start loading until something renders it +// it won't start loading until something renders it const GraphFeedItem = React.lazy(() => import('./GraphFeedItem')) function Feed() { @@ -334,10 +354,10 @@ function Feed() { for (const feedItem of feed) { if (feedItem.type === 'GRAPH') { - queryClient.prefetchQuery({ + void queryClient.query({ queryKey: ['graph', feedItem.id], queryFn: getGraphDataById, - }) + }).catch(noop) } } @@ -373,6 +393,8 @@ For now, let's focus on the client side case and look at an example of how you c When integrating at the router level, you can choose to either _block_ rendering of that route until all data is present, or you can start a prefetch but not await the result. That way, you can start rendering the route as soon as possible. You can also mix these two approaches and await some critical data, but start rendering before all the secondary data has finished loading. In this example, we'll configure an `/article` route to not render until the article data has finished loading, as well as start prefetching comments as soon as possible, but not block rendering the route if comments haven't finished loading yet. +Note that many route loaders use error boundaries to trigger error fallbacks. Whereas up to now we have been using `.catch(noop)` to ignore errors for data that will be retried by `useQuery`, for critical data that the route will not work without, you should `await` the promise without `noop` and handle the error in a `try` block or the router's error handling (such as TanStack Router's `errorComponent`). + ```tsx const queryClient = new QueryClient() const routerContext = new RouterContext() @@ -393,11 +415,19 @@ const articleRoute = new Route({ context: { queryClient }, routeContext: { articleQueryOptions, commentsQueryOptions }, }) => { - // Fetch comments asap, but don't block - queryClient.prefetchQuery(commentsQueryOptions) + // Fetch comments asap, but don't block or throw errors + void queryClient.query(commentsQueryOptions).catch(noop) // Don't render the route at all until article has been fetched - await queryClient.prefetchQuery(articleQueryOptions) + // As this is critical data we want the error component to trigger + // as soon as possible if something goes wrong + await queryClient.query({ + ...articleQueryOptions, + // If we have the article loaded already, we don't want to block on + // an extra prefetch; fallback on the default useQuery behavior to + // keep the data fresh + staleTime: 'static' + }) }, component: ({ useRouteContext }) => { const { articleQueryOptions, commentsQueryOptions } = useRouteContext() diff --git a/docs/framework/react/guides/query-options.md b/docs/framework/react/guides/query-options.md index 9a33a2a2154..c994cc0d9be 100644 --- a/docs/framework/react/guides/query-options.md +++ b/docs/framework/react/guides/query-options.md @@ -25,7 +25,7 @@ useSuspenseQuery(groupOptions(5)) useQueries({ queries: [groupOptions(1), groupOptions(2)], }) -queryClient.prefetchQuery(groupOptions(23)) +queryClient.query(groupOptions(23)) queryClient.setQueryData(groupOptions(42).queryKey, newGroups) ``` diff --git a/docs/framework/react/guides/ssr.md b/docs/framework/react/guides/ssr.md index 066f6e94e3d..402f718f7da 100644 --- a/docs/framework/react/guides/ssr.md +++ b/docs/framework/react/guides/ssr.md @@ -170,7 +170,7 @@ Setting up the full hydration solution is straightforward and does not have thes With just a little more setup, you can use a `queryClient` to prefetch queries during a preload phase, pass a serialized version of that `queryClient` to the rendering part of the app and reuse it there. This avoids the drawbacks above. Feel free to skip ahead for full Next.js pages router and Remix examples, but at a general level these are the extra steps: - In the framework loader function, create a `const queryClient = new QueryClient(options)` -- In the loader function, do `await queryClient.prefetchQuery(...)` for each query you want to prefetch +- In the loader function, do `await queryClient.query(...)` for each query you want to prefetch - You want to use `await Promise.all(...)` to fetch the queries in parallel when possible - It's fine to have queries that aren't prefetched. These wont be server rendered, instead they will be fetched on the client after the application is interactive. This can be great for content that are shown only after user interaction, or is far down on the page to avoid blocking more critical content. - From the loader, return `dehydrate(queryClient)`, note that the exact syntax to return this differs between frameworks @@ -228,10 +228,12 @@ import { export async function getStaticProps() { const queryClient = new QueryClient() - await queryClient.prefetchQuery({ - queryKey: ['posts'], - queryFn: getPosts, - }) + await queryClient + .query({ + queryKey: ['posts'], + queryFn: getPosts, + }) + .catch(noop) return { props: { @@ -310,10 +312,12 @@ import { export async function loader() { const queryClient = new QueryClient() - await queryClient.prefetchQuery({ - queryKey: ['posts'], - queryFn: getPosts, - }) + await queryClient + .query({ + queryKey: ['posts'], + queryFn: getPosts, + }) + .catch(noop) return json({ dehydratedState: dehydrate(queryClient) }) } @@ -419,13 +423,13 @@ How would we prefetch this so it can be server rendered? Here's an example: export async function getServerSideProps() { const queryClient = new QueryClient() - const user = await queryClient.fetchQuery({ + const user = await queryClient.query({ queryKey: ['user', email], queryFn: getUserByEmail, }) if (user?.userId) { - await queryClient.prefetchQuery({ + await queryClient.query({ queryKey: ['projects', userId], queryFn: getProjectsByUser, }) @@ -443,18 +447,18 @@ This can get more complex of course, but since these loader functions are just J React Query defaults to a graceful degradation strategy. This means: -- `queryClient.prefetchQuery(...)` never throws errors - `dehydrate(...)` only includes successful queries, not failed ones +- We can intentionally ignore the returned promise from `void queryClient.query(...)` and add `.catch(noop)` to swallow any errors, so surrounding loader code will not observe query errors This will lead to any failed queries being retried on the client and that the server rendered output will include loading states instead of the full content. -While a good default, sometimes this is not what you want. When critical content is missing, you might want to respond with a 404 or 500 status code depending on the situation. For these cases, use `queryClient.fetchQuery(...)` instead, which will throw errors when it fails, letting you handle things in a suitable way. +While a good default, sometimes this is not what you want. When critical content is missing, you might want to respond with a 404 or 500 status code depending on the situation. For these cases, use `await queryClient.query(...)` without the noop catch, which will throw errors when it fails, letting you handle things in a suitable way. ```tsx let result try { - result = await queryClient.fetchQuery(...) + result = await queryClient.query(...) } catch (error) { // Handle the error, refer to your framework documentation } diff --git a/docs/framework/react/reference/infiniteQueryOptions.md b/docs/framework/react/reference/infiniteQueryOptions.md index 743f99438ab..5455c2a440f 100644 --- a/docs/framework/react/reference/infiniteQueryOptions.md +++ b/docs/framework/react/reference/infiniteQueryOptions.md @@ -12,7 +12,7 @@ infiniteQueryOptions({ **Options** -You can generally pass everything to `infiniteQueryOptions` that you can also pass to [`useInfiniteQuery`](./useInfiniteQuery.md). Some options will have no effect when then forwarded to a function like `queryClient.prefetchInfiniteQuery`, but TypeScript will still be fine with those excess properties. +You can generally pass everything to `infiniteQueryOptions` that you can also pass to [`useInfiniteQuery`](./useInfiniteQuery.md). These options can be shared across hooks and imperative APIs such as `queryClient.infiniteQuery`. - `queryKey: QueryKey` - **Required** diff --git a/docs/framework/react/reference/queryOptions.md b/docs/framework/react/reference/queryOptions.md index b6c5409372d..f20edf312d3 100644 --- a/docs/framework/react/reference/queryOptions.md +++ b/docs/framework/react/reference/queryOptions.md @@ -12,7 +12,7 @@ queryOptions({ **Options** -You can generally pass everything to `queryOptions` that you can also pass to [`useQuery`](./useQuery.md). Some options will have no effect when then forwarded to a function like `queryClient.prefetchQuery`, but TypeScript will still be fine with those excess properties. +You can generally pass everything to `queryOptions` that you can also pass to [`useQuery`](./useQuery.md). These options can be shared across hooks and imperative APIs such as `queryClient.query`. - `queryKey: QueryKey` - **Required** diff --git a/docs/framework/react/reference/usePrefetchInfiniteQuery.md b/docs/framework/react/reference/usePrefetchInfiniteQuery.md index 1e86a35b55d..10d85e429d0 100644 --- a/docs/framework/react/reference/usePrefetchInfiniteQuery.md +++ b/docs/framework/react/reference/usePrefetchInfiniteQuery.md @@ -9,7 +9,7 @@ usePrefetchInfiniteQuery(options) **Options** -You can pass everything to `usePrefetchInfiniteQuery` that you can pass to [`queryClient.prefetchInfiniteQuery`](../../../reference/QueryClient.md#queryclientprefetchinfinitequery). Remember that some of them are required as below: +You can pass everything to `usePrefetchInfiniteQuery` that you can pass to [`queryClient.infiniteQuery`](../../../reference/QueryClient.md#queryclientinfinitequery). Remember that some of them are required as below: - `queryKey: QueryKey` - **Required** diff --git a/docs/framework/react/reference/usePrefetchQuery.md b/docs/framework/react/reference/usePrefetchQuery.md index 7feccad5fc7..c1d2e50e8bc 100644 --- a/docs/framework/react/reference/usePrefetchQuery.md +++ b/docs/framework/react/reference/usePrefetchQuery.md @@ -9,13 +9,13 @@ usePrefetchQuery(options) **Options** -You can pass everything to `usePrefetchQuery` that you can pass to [`queryClient.prefetchQuery`](../../../reference/QueryClient.md#queryclientprefetchquery). Remember that some of them are required as below: +You can pass everything to `usePrefetchQuery` that you can pass to [`queryClient.query`](../../../reference/QueryClient.md#queryclientquery). Remember that some of them are required as below: - `queryKey: QueryKey` - **Required** - The query key to prefetch during render -- `queryFn: (context: QueryFunctionContext) => Promise` +- `queryFn: (context: QueryFunctionContext) => TQueryFnData | Promise` - **Required, but only if no default query function has been defined** See [Default Query Function](../guides/default-query-function.md) for more information. **Returns** diff --git a/docs/framework/react/typescript.md b/docs/framework/react/typescript.md index ee15ff855db..975b5c1d977 100644 --- a/docs/framework/react/typescript.md +++ b/docs/framework/react/typescript.md @@ -199,7 +199,7 @@ declare module '@tanstack/react-query' { ## Typing Query Options -If you inline query options into `useQuery`, you'll get automatic type inference. However, you might want to extract the query options into a separate function to share them between `useQuery` and e.g. `prefetchQuery`. In that case, you'd lose type inference. To get it back, you can use the `queryOptions` helper: +If you inline query options into `useQuery`, you'll get automatic type inference. However, you might want to extract the query options into a separate function to share them between `useQuery` and e.g. `query`. In that case, you'd lose type inference. To get it back, you can use the `queryOptions` helper: ```ts import { queryOptions } from '@tanstack/react-query' @@ -213,7 +213,7 @@ function groupOptions() { } useQuery(groupOptions()) -queryClient.prefetchQuery(groupOptions()) +queryClient.query(groupOptions()) ``` Further, the `queryKey` returned from `queryOptions` knows about the `queryFn` associated with it, and we can leverage that type information to make functions like `queryClient.getQueryData` aware of those types as well: diff --git a/docs/framework/solid/guides/prefetching.md b/docs/framework/solid/guides/prefetching.md index 8c7a7720020..903da8435b2 100644 --- a/docs/framework/solid/guides/prefetching.md +++ b/docs/framework/solid/guides/prefetching.md @@ -2,6 +2,7 @@ id: prefetching title: Prefetching & Router Integration ref: docs/framework/react/guides/prefetching.md +replace: { 'react-query': 'solid-query', 'React': 'Solid' } --- [//]: # 'ExampleComponent' @@ -86,17 +87,19 @@ function Comments(props) { [//]: # 'ExampleParentComponent' [//]: # 'Suspense' -Another way is to prefetch inside of the query function. This makes sense if you know that every time an article is fetched it's very likely comments will also be needed. For this, we'll use `queryClient.prefetchQuery`: +Another way is to prefetch inside the query function. This makes sense if you know that every time an article is fetched it's very likely comments will also be needed. For this, we'll use `queryClient.query`: ```tsx const queryClient = useQueryClient() const articleQuery = useQuery(() => ({ queryKey: ['article', id], queryFn: (...args) => { - queryClient.prefetchQuery({ - queryKey: ['article-comments', id], - queryFn: getArticleCommentsById, - }) + void queryClient + .query({ + queryKey: ['article-comments', id], + queryFn: getArticleCommentsById, + }) + .catch(noop) return getArticleById(...args) }, @@ -111,10 +114,12 @@ import { createEffect } from 'solid-js' const queryClient = useQueryClient() createEffect(() => { - queryClient.prefetchQuery({ - queryKey: ['article-comments', id], - queryFn: getArticleCommentsById, - }) + void queryClient + .query({ + queryKey: ['article-comments', id], + queryFn: getArticleCommentsById, + }) + .catch(noop) }) ``` @@ -185,10 +190,10 @@ function Feed() { for (const feedItem of feed) { if (feedItem.type === 'GRAPH') { - queryClient.prefetchQuery({ + void queryClient.query({ queryKey: ['graph', feedItem.id], queryFn: getGraphDataById, - }) + }).catch(noop) } } @@ -213,6 +218,8 @@ For now, let's focus on the client side case and look at an example of how you c When integrating at the router level, you can choose to either _block_ rendering of that route until all data is present, or you can start a prefetch but not await the result. That way, you can start rendering the route as soon as possible. You can also mix these two approaches and await some critical data, but start rendering before all the secondary data has finished loading. In this example, we'll configure an `/article` route to not render until the article data has finished loading, as well as start prefetching comments as soon as possible, but not block rendering the route if comments haven't finished loading yet. +Note that many route loaders use error boundaries to trigger error fallbacks. Whereas up to now we have been using `.catch(noop)` to ignore errors for data that will be retried by `useQuery`, for critical data that the route will not work without, you should `await` the promise without `noop` and handle the error in a `try` block or the router's error handling (such as TanStack Router's `errorComponent`). + ```tsx const queryClient = new QueryClient() const routerContext = new RouterContext() @@ -233,11 +240,19 @@ const articleRoute = new Route({ context: { queryClient }, routeContext: { articleQueryOptions, commentsQueryOptions }, }) => { - // Fetch comments asap, but don't block - queryClient.prefetchQuery(commentsQueryOptions) + // Fetch comments asap, but don't block or throw errors + void queryClient.query(commentsQueryOptions).catch(noop) // Don't render the route at all until article has been fetched - await queryClient.prefetchQuery(articleQueryOptions) + // As this is critical data we want the error component to trigger + // as soon as possible if something goes wrong + await queryClient.query({ + ...articleQueryOptions, + // If we have the article loaded already, we don't want to block on + // an extra prefetch; fallback on the default useQuery behavior to + // keep the data fresh + staleTime: 'static' + }) }, component: ({ useRouteContext }) => { const { articleQueryOptions, commentsQueryOptions } = useRouteContext() diff --git a/docs/framework/solid/guides/query-options.md b/docs/framework/solid/guides/query-options.md index d5a625962b9..7687bc94b18 100644 --- a/docs/framework/solid/guides/query-options.md +++ b/docs/framework/solid/guides/query-options.md @@ -32,7 +32,7 @@ useQuery(() => groupOptions(1)) useQueries(() => ({ queries: [groupOptions(1), groupOptions(2)], })) -queryClient.prefetchQuery(groupOptions(23)) +queryClient.query(groupOptions(23)) queryClient.setQueryData(groupOptions(42).queryKey, newGroups) ``` diff --git a/docs/framework/solid/typescript.md b/docs/framework/solid/typescript.md index 90a3c8027c8..cf9574a414b 100644 --- a/docs/framework/solid/typescript.md +++ b/docs/framework/solid/typescript.md @@ -173,7 +173,7 @@ declare module '@tanstack/solid-query' { ## Typing Query Options -If you inline query options into `useQuery`, you'll get automatic type inference. However, you might want to extract the query options into a separate function to share them between `useQuery` and e.g. `prefetchQuery`. In that case, you'd lose type inference. To get it back, you can use `queryOptions` helper: +If you inline query options into `useQuery`, you'll get automatic type inference. However, you might want to extract the query options into a separate function to share them between `useQuery` and e.g `query`. In that case, you'd lose type inference. To get it back, you can use `queryOptions` helper: ```ts import { queryOptions } from '@tanstack/solid-query' @@ -187,7 +187,7 @@ function groupOptions() { } useQuery(groupOptions) -queryClient.prefetchQuery(groupOptions()) +queryClient.query(groupOptions()) ``` Further, the `queryKey` returned from `queryOptions` knows about the `queryFn` associated with it, and we can leverage that type information to make functions like `queryClient.getQueryData` aware of those types as well: diff --git a/docs/framework/svelte/ssr.md b/docs/framework/svelte/ssr.md index c9eccca4269..028e2425eab 100644 --- a/docs/framework/svelte/ssr.md +++ b/docs/framework/svelte/ssr.md @@ -7,7 +7,7 @@ title: SSR and SvelteKit SvelteKit defaults to rendering routes with SSR. Because of this, you need to disable the query on the server. Otherwise, your query will continue executing on the server asynchronously, even after the HTML has been sent to the client. -The recommended way to achieve this is to use the `browser` module from SvelteKit in your `QueryClient` object. This will not disable `queryClient.prefetchQuery()`, which is used in one of the solutions below. +The recommended way to achieve this is to use the `browser` module from SvelteKit in your `QueryClient` object. This will not disable `queryClient.query()`, which is used in one of the solutions below. **src/routes/+layout.svelte** @@ -77,7 +77,7 @@ Cons: - If you are calling `createQuery` with the same query in multiple locations, you need to pass `initialData` to all of them - There is no way to know at what time the query was fetched on the server, so `dataUpdatedAt` and determining if the query needs refetching is based on when the page loaded instead -### Using `prefetchQuery` +### Using `query` Svelte Query supports prefetching queries on the server. Using this setup below, you can fetch data and pass it into QueryClientProvider before it is sent to the user's browser. Therefore, this data is already available in the cache, and no initial fetch occurs client-side. @@ -122,10 +122,12 @@ export async function load({ parent, fetch }) { const { queryClient } = await parent() // You need to use the SvelteKit fetch function here - await queryClient.prefetchQuery({ - queryKey: ['posts'], - queryFn: async () => (await fetch('/api/posts')).json(), - }) + await queryClient + .query({ + queryKey: ['posts'], + queryFn: async () => (await fetch('/api/posts')).json(), + }) + .catch(noop) } ``` @@ -135,7 +137,7 @@ export async function load({ parent, fetch }) { ``` -As demonstrated, it's fine to prefetch some queries and let others fetch on the queryClient. This means you can control what content server renders or not by adding or removing `prefetchQuery` or `suspense` for a specific query. +As demonstrated, it's fine to prefetch some queries and let others fetch on the client. This means you can control what content server renders or not by adding or removing `queryClient.query` or `suspense` for a specific query. ## Using Vite SSR @@ -237,7 +238,7 @@ Then, call VueQuery from any component using Vue's `onServerPrefetch`: Any query with an error is automatically excluded from dehydration. This means that the default behavior is to pretend these queries were never loaded on the server, usually showing a loading state instead, and retrying the queries on the queryClient. This happens regardless of error. -Sometimes this behavior is not desirable, maybe you want to render an error page with a correct status code instead on certain errors or queries. In those cases, use `fetchQuery` and catch any errors to handle those manually. +Sometimes this behavior is not desirable, maybe you want to render an error page with a correct status code instead on certain errors or queries. In those cases, use `queryClient.query` and catch any errors to handle those manually. ### Staleness is measured from when the query was fetched on the server diff --git a/docs/reference/QueryClient.md b/docs/reference/QueryClient.md index 13bde1ffad0..487dacf65d5 100644 --- a/docs/reference/QueryClient.md +++ b/docs/reference/QueryClient.md @@ -18,18 +18,14 @@ const queryClient = new QueryClient({ }, }) -await queryClient.prefetchQuery({ queryKey: ['posts'], queryFn: fetchPosts }) +await queryClient.query({ queryKey: ['posts'], queryFn: fetchPosts }) ``` Its available methods are: -- [`queryClient.fetchQuery`](#queryclientfetchquery) -- [`queryClient.fetchInfiniteQuery`](#queryclientfetchinfinitequery) -- [`queryClient.prefetchQuery`](#queryclientprefetchquery) -- [`queryClient.prefetchInfiniteQuery`](#queryclientprefetchinfinitequery) +- [`queryClient.query`](#queryclientquery) +- [`queryClient.infiniteQuery`](#queryclientinfinitequery) - [`queryClient.getQueryData`](#queryclientgetquerydata) -- [`queryClient.ensureQueryData`](#queryclientensurequerydata) -- [`queryClient.ensureInfiniteQueryData`](#queryclientensureinfinitequerydata) - [`queryClient.getQueriesData`](#queryclientgetqueriesdata) - [`queryClient.setQueryData`](#queryclientsetquerydata) - [`queryClient.getQueryState`](#queryclientgetquerystate) @@ -65,15 +61,15 @@ Its available methods are: - Define defaults for all queries and mutations using this queryClient. - You can also define defaults to be used for [hydration](../framework/react/reference/hydration.md) -## `queryClient.fetchQuery` +## `queryClient.query` -`fetchQuery` is an asynchronous method that can be used to fetch and cache a query. It will either resolve with the data or throw with the error. Use the `prefetchQuery` method if you just want to fetch a query without needing the result. +`query` is an asynchronous method that can be used to fetch and cache a query. It will either resolve with the data or throw with an error. If the query exists and the data is not invalidated or older than the given `staleTime`, then the data from the cache will be returned. Otherwise it will try to fetch the latest data. ```tsx try { - const data = await queryClient.fetchQuery({ queryKey, queryFn }) + const data = await queryClient.query({ queryKey, queryFn }) } catch (error) { console.log(error) } @@ -83,10 +79,11 @@ Specify a `staleTime` to only fetch when the data is older than a certain amount ```tsx try { - const data = await queryClient.fetchQuery({ + const data = await queryClient.query({ queryKey, queryFn, staleTime: 10000, + select: (data) => data.items, }) } catch (error) { console.log(error) @@ -95,19 +92,19 @@ try { **Options** -The options for `fetchQuery` are exactly the same as those of [`useQuery`](../framework/react/reference/useQuery.md), except the following: `enabled, refetchInterval, refetchIntervalInBackground, refetchOnWindowFocus, refetchOnReconnect, refetchOnMount, notifyOnChangeProps, throwOnError, select, suspense, placeholderData`; which are strictly for useQuery and useInfiniteQuery. You can check the [source code](https://github.com/TanStack/query/blob/7cd2d192e6da3df0b08e334ea1cf04cd70478827/packages/query-core/src/types.ts#L119) for more clarity. +The options for `query` are exactly the same as those of [`useQuery`](../framework/react/reference/useQuery.md), except the following: `refetchInterval, refetchIntervalInBackground, refetchOnWindowFocus, refetchOnReconnect, refetchOnMount, notifyOnChangeProps, throwOnError, suspense, placeholderData`; which are strictly for useQuery and useInfiniteQuery. You can check the [source code](https://github.com/TanStack/query/blob/7cd2d192e6da3df0b08e334ea1cf04cd70478827/packages/query-core/src/types.ts#L119) for more clarity. **Returns** - `Promise` -## `queryClient.fetchInfiniteQuery` +## `queryClient.infiniteQuery` -`fetchInfiniteQuery` is similar to `fetchQuery` but can be used to fetch and cache an infinite query. +`infiniteQuery` is similar to `query` but can be used to fetch and cache an infinite query. ```tsx try { - const data = await queryClient.fetchInfiniteQuery({ queryKey, queryFn }) + const data = await queryClient.infiniteQuery({ queryKey, queryFn }) console.log(data.pages) } catch (error) { console.log(error) @@ -116,109 +113,7 @@ try { **Options** -The options for `fetchInfiniteQuery` are exactly the same as those of [`fetchQuery`](#queryclientfetchquery). - -**Returns** - -- `Promise>` - -## `queryClient.prefetchQuery` - -`prefetchQuery` is an asynchronous method that can be used to prefetch a query before it is needed or rendered with `useQuery` and friends. The method works the same as `fetchQuery` except that it will not throw or return any data. - -```tsx -await queryClient.prefetchQuery({ queryKey, queryFn }) -``` - -You can even use it with a default queryFn in your config! - -```tsx -await queryClient.prefetchQuery({ queryKey }) -``` - -**Options** - -The options for `prefetchQuery` are exactly the same as those of [`fetchQuery`](#queryclientfetchquery). - -**Returns** - -- `Promise` - - A promise is returned that will either immediately resolve if no fetch is needed or after the query has been executed. It will not return any data or throw any errors. - -## `queryClient.prefetchInfiniteQuery` - -`prefetchInfiniteQuery` is similar to `prefetchQuery` but can be used to prefetch and cache an infinite query. - -```tsx -await queryClient.prefetchInfiniteQuery({ queryKey, queryFn }) -``` - -**Options** - -The options for `prefetchInfiniteQuery` are exactly the same as those of [`fetchQuery`](#queryclientfetchquery). - -**Returns** - -- `Promise` - - A promise is returned that will either immediately resolve if no fetch is needed or after the query has been executed. It will not return any data or throw any errors. - -## `queryClient.getQueryData` - -`getQueryData` is a synchronous function that can be used to get an existing query's cached data. If the query does not exist, `undefined` will be returned. - -```tsx -const data = queryClient.getQueryData(queryKey) -``` - -**Options** - -- `queryKey: QueryKey`: [Query Keys](../framework/react/guides/query-keys.md) - -**Returns** - -- `data: TQueryFnData | undefined` - - The data for the cached query, or `undefined` if the query does not exist. - -## `queryClient.ensureQueryData` - -`ensureQueryData` is an asynchronous function that can be used to get an existing query's cached data. If the query does not exist, `queryClient.fetchQuery` will be called and its results returned. - -```tsx -const data = await queryClient.ensureQueryData({ queryKey, queryFn }) -``` - -**Options** - -- the same options as [`fetchQuery`](#queryclientfetchquery) -- `revalidateIfStale: boolean` - - Optional - - Defaults to `false` - - If set to `true`, stale data will be refetched in the background, but cached data will be returned immediately. - -**Returns** - -- `Promise` - -## `queryClient.ensureInfiniteQueryData` - -`ensureInfiniteQueryData` is an asynchronous function that can be used to get an existing infinite query's cached data. If the query does not exist, `queryClient.fetchInfiniteQuery` will be called and its results returned. - -```tsx -const data = await queryClient.ensureInfiniteQueryData({ - queryKey, - queryFn, - initialPageParam, - getNextPageParam, -}) -``` - -**Options** - -- the same options as [`fetchInfiniteQuery`](#queryclientfetchinfinitequery) -- `revalidateIfStale: boolean` - - Optional - - Defaults to `false` - - If set to `true`, stale data will be refetched in the background, but cached data will be returned immediately. +The options for `infiniteQuery` are exactly the same as those of [`query`](#queryclientquery), with the addition of `initialPageParam`, `pages` and `getNextPageParam` options from [`useInfiniteQuery`](../framework/react/reference/useInfiniteQuery.md). **Returns** @@ -252,7 +147,7 @@ This distinction is more a "convenience" for ts devs that know which structure w `setQueryData` is a synchronous function that can be used to immediately update a query's cached data. If the query does not exist, it will be created. **If the query is not utilized by a query hook in the default `gcTime` of 5 minutes, the query will be garbage collected**. To update multiple queries at once and match query keys partially, you need to use [`queryClient.setQueriesData`](#queryclientsetqueriesdata) instead. -> The difference between using `setQueryData` and `fetchQuery` is that `setQueryData` is sync and assumes that you already synchronously have the data available. If you need to fetch the data asynchronously, it's suggested that you either refetch the query key or use `fetchQuery` to handle the asynchronous fetch. +> The difference between using `setQueryData` and `query` is that `setQueryData` is sync and assumes that you already synchronously have the data available. If you need to fetch the data asynchronously, it's suggested that you either refetch the query key or use `query` to handle the asynchronous fetch. ```tsx queryClient.setQueryData(queryKey, updater) diff --git a/packages/angular-query-experimental/src/__tests__/infinite-query-options.test-d.ts b/packages/angular-query-experimental/src/__tests__/infinite-query-options.test-d.ts index 17ce85b9d46..de850fdd768 100644 --- a/packages/angular-query-experimental/src/__tests__/infinite-query-options.test-d.ts +++ b/packages/angular-query-experimental/src/__tests__/infinite-query-options.test-d.ts @@ -67,6 +67,34 @@ describe('infiniteQueryOptions', () => { expectTypeOf(data).toEqualTypeOf>() }) + + it('should work when passed to infiniteQuery', async () => { + const options = infiniteQueryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + getNextPageParam: () => 1, + initialPageParam: 1, + }) + + const data = await new QueryClient().infiniteQuery(options) + + expectTypeOf(data).toEqualTypeOf>() + }) + + it('should work when passed to infiniteQuery with select', async () => { + const options = infiniteQueryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + getNextPageParam: () => 1, + initialPageParam: 1, + select: (data) => data.pages, + }) + + const data = await new QueryClient().infiniteQuery(options) + + expectTypeOf(data).toEqualTypeOf>() + }) + it('should tag the queryKey with the result type of the QueryFn', () => { const key = queryKey() const { queryKey: tagged } = infiniteQueryOptions({ diff --git a/packages/angular-query-experimental/src/__tests__/query-options.test-d.ts b/packages/angular-query-experimental/src/__tests__/query-options.test-d.ts index cfd1890d1c4..baf5fbc115c 100644 --- a/packages/angular-query-experimental/src/__tests__/query-options.test-d.ts +++ b/packages/angular-query-experimental/src/__tests__/query-options.test-d.ts @@ -67,6 +67,27 @@ test('should work when passed to fetchQuery', () => { assertType>(data) }) +test('should work when passed to query', () => { + const options = queryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + }) + + const data = new QueryClient().query(options) + assertType>(data) +}) + +test('should work when passed to query with select', () => { + const options = queryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + select: (data) => data.toString(), + }) + + const data = new QueryClient().query(options) + assertType>(data) +}) + test('should tag the queryKey with the result type of the QueryFn', () => { const key = queryKey() const { queryKey: tagged } = queryOptions({ diff --git a/packages/preact-query/src/__tests__/infiniteQueryOptions.test-d.tsx b/packages/preact-query/src/__tests__/infiniteQueryOptions.test-d.tsx index bf3daec3355..0d0d7587437 100644 --- a/packages/preact-query/src/__tests__/infiniteQueryOptions.test-d.tsx +++ b/packages/preact-query/src/__tests__/infiniteQueryOptions.test-d.tsx @@ -64,6 +64,31 @@ describe('infiniteQueryOptions', () => { expectTypeOf(data).toEqualTypeOf>() }) + it('should work when passed to infiniteQuery', async () => { + const options = infiniteQueryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + getNextPageParam: () => 1, + initialPageParam: 1, + }) + + const data = await new QueryClient().infiniteQuery(options) + + expectTypeOf(data).toEqualTypeOf>() + }) + it('should work when passed to infiniteQuery with select', async () => { + const options = infiniteQueryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + getNextPageParam: () => 1, + initialPageParam: 1, + select: (data) => data.pages, + }) + + const data = await new QueryClient().infiniteQuery(options) + + expectTypeOf(data).toEqualTypeOf>() + }) it('should work when passed to fetchInfiniteQuery', async () => { const options = infiniteQueryOptions({ queryKey: queryKey(), diff --git a/packages/preact-query/src/__tests__/queryOptions.test-d.tsx b/packages/preact-query/src/__tests__/queryOptions.test-d.tsx index 6f0d34c6bac..f8610466dc2 100644 --- a/packages/preact-query/src/__tests__/queryOptions.test-d.tsx +++ b/packages/preact-query/src/__tests__/queryOptions.test-d.tsx @@ -57,7 +57,25 @@ describe('queryOptions', () => { const { data } = useSuspenseQuery(options) expectTypeOf(data).toEqualTypeOf() }) + it('should work when passed to query', async () => { + const options = queryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + }) + const data = await new QueryClient().query(options) + expectTypeOf(data).toEqualTypeOf() + }) + it('should work when passed to query with select', async () => { + const options = queryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + select: (data) => data.toString(), + }) + + const data = await new QueryClient().query(options) + expectTypeOf(data).toEqualTypeOf() + }) it('should work when passed to fetchQuery', async () => { const options = queryOptions({ queryKey: queryKey(), diff --git a/packages/preact-query/src/__tests__/useInfiniteQuery.test-d.tsx b/packages/preact-query/src/__tests__/useInfiniteQuery.test-d.tsx index b11002c9e2c..cb96b99fa7e 100644 --- a/packages/preact-query/src/__tests__/useInfiniteQuery.test-d.tsx +++ b/packages/preact-query/src/__tests__/useInfiniteQuery.test-d.tsx @@ -39,6 +39,18 @@ describe('pageParam', () => { }) }) + it('initialPageParam should define type of param passed to queryFunctionContext for infiniteQuery', () => { + const queryClient = new QueryClient() + queryClient.infiniteQuery({ + queryKey: ['key'], + queryFn: ({ pageParam }) => { + expectTypeOf(pageParam).toEqualTypeOf() + return Promise.resolve(pageParam) + }, + initialPageParam: 1, + }) + }) + it('initialPageParam should define type of param passed to queryFunctionContext for prefetchInfiniteQuery', () => { const queryClient = new QueryClient() queryClient.prefetchInfiniteQuery({ diff --git a/packages/preact-query/src/__tests__/usePrefetchInfiniteQuery.test-d.tsx b/packages/preact-query/src/__tests__/usePrefetchInfiniteQuery.test-d.tsx index 048f340991f..a5bfe5ec2d8 100644 --- a/packages/preact-query/src/__tests__/usePrefetchInfiniteQuery.test-d.tsx +++ b/packages/preact-query/src/__tests__/usePrefetchInfiniteQuery.test-d.tsx @@ -25,7 +25,7 @@ describe('usePrefetchInfiniteQuery', () => { ) }) - it('should not allow refetchInterval, enabled or throwOnError options', () => { + it('should not allow refetchInterval or throwOnError options', () => { assertType( usePrefetchInfiniteQuery({ queryKey: queryKey(), @@ -37,17 +37,6 @@ describe('usePrefetchInfiniteQuery', () => { }), ) - assertType( - usePrefetchInfiniteQuery({ - queryKey: queryKey(), - queryFn: () => Promise.resolve(5), - initialPageParam: 1, - getNextPageParam: () => 1, - // @ts-expect-error TS2353 - enabled: true, - }), - ) - assertType( usePrefetchInfiniteQuery({ queryKey: queryKey(), diff --git a/packages/preact-query/src/__tests__/usePrefetchQuery.test-d.tsx b/packages/preact-query/src/__tests__/usePrefetchQuery.test-d.tsx index 6f5d5102514..d2754968ef4 100644 --- a/packages/preact-query/src/__tests__/usePrefetchQuery.test-d.tsx +++ b/packages/preact-query/src/__tests__/usePrefetchQuery.test-d.tsx @@ -1,7 +1,7 @@ import { queryKey } from '@tanstack/query-test-utils' import { assertType, describe, expectTypeOf, it } from 'vitest' -import { skipToken, usePrefetchQuery } from '..' +import { usePrefetchQuery } from '..' describe('usePrefetchQuery', () => { it('should return nothing', () => { @@ -13,7 +13,7 @@ describe('usePrefetchQuery', () => { expectTypeOf(result).toEqualTypeOf() }) - it('should not allow refetchInterval, enabled or throwOnError options', () => { + it('should not allow refetchInterval or throwOnError options', () => { assertType( usePrefetchQuery({ queryKey: queryKey(), @@ -23,15 +23,6 @@ describe('usePrefetchQuery', () => { }), ) - assertType( - usePrefetchQuery({ - queryKey: queryKey(), - queryFn: () => Promise.resolve(5), - // @ts-expect-error TS2345 - enabled: true, - }), - ) - assertType( usePrefetchQuery({ queryKey: queryKey(), @@ -41,21 +32,4 @@ describe('usePrefetchQuery', () => { }), ) }) - - it('should not allow skipToken in queryFn', () => { - assertType( - usePrefetchQuery({ - queryKey: queryKey(), - // @ts-expect-error - queryFn: skipToken, - }), - ) - assertType( - usePrefetchQuery({ - queryKey: queryKey(), - // @ts-expect-error - queryFn: Math.random() > 0.5 ? skipToken : () => Promise.resolve(5), - }), - ) - }) }) diff --git a/packages/preact-query/src/types.ts b/packages/preact-query/src/types.ts index c995a28b08e..8894d40fb6c 100644 --- a/packages/preact-query/src/types.ts +++ b/packages/preact-query/src/types.ts @@ -4,7 +4,6 @@ import type { DefinedInfiniteQueryObserverResult, DefinedQueryObserverResult, DistributiveOmit, - FetchQueryOptions, InfiniteQueryObserverOptions, InfiniteQueryObserverResult, MutateFunction, @@ -45,21 +44,6 @@ export interface UseBaseQueryOptions< subscribed?: boolean } -export interface UsePrefetchQueryOptions< - TQueryFnData = unknown, - TError = DefaultError, - TData = TQueryFnData, - TQueryKey extends QueryKey = QueryKey, -> extends OmitKeyof< - FetchQueryOptions, - 'queryFn' -> { - queryFn?: Exclude< - FetchQueryOptions['queryFn'], - SkipToken - > -} - export type AnyUseQueryOptions = UseQueryOptions export interface UseQueryOptions< TQueryFnData = unknown, diff --git a/packages/preact-query/src/usePrefetchInfiniteQuery.tsx b/packages/preact-query/src/usePrefetchInfiniteQuery.tsx index e408e345220..989779d0112 100644 --- a/packages/preact-query/src/usePrefetchInfiniteQuery.tsx +++ b/packages/preact-query/src/usePrefetchInfiniteQuery.tsx @@ -1,8 +1,9 @@ +import { noop } from '@tanstack/query-core' import type { DefaultError, - FetchInfiniteQueryOptions, QueryClient, QueryKey, + InfiniteQueryExecuteOptions, } from '@tanstack/query-core' import { useQueryClient } from './QueryClientProvider' @@ -14,7 +15,7 @@ export function usePrefetchInfiniteQuery< TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, >( - options: FetchInfiniteQueryOptions< + options: InfiniteQueryExecuteOptions< TQueryFnData, TError, TData, @@ -26,6 +27,6 @@ export function usePrefetchInfiniteQuery< const client = useQueryClient(queryClient) if (!client.getQueryState(options.queryKey)) { - client.prefetchInfiniteQuery(options) + void client.infiniteQuery(options).then(noop).catch(noop) } } diff --git a/packages/preact-query/src/usePrefetchQuery.tsx b/packages/preact-query/src/usePrefetchQuery.tsx index e7a2a346340..b59df2ecf18 100644 --- a/packages/preact-query/src/usePrefetchQuery.tsx +++ b/packages/preact-query/src/usePrefetchQuery.tsx @@ -1,7 +1,12 @@ -import type { DefaultError, QueryClient, QueryKey } from '@tanstack/query-core' +import { noop } from '@tanstack/query-core' +import type { + DefaultError, + QueryClient, + QueryKey, + QueryExecuteOptions, +} from '@tanstack/query-core' import { useQueryClient } from './QueryClientProvider' -import type { UsePrefetchQueryOptions } from './types' export function usePrefetchQuery< TQueryFnData = unknown, @@ -9,12 +14,12 @@ export function usePrefetchQuery< TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, >( - options: UsePrefetchQueryOptions, + options: QueryExecuteOptions, queryClient?: QueryClient, ) { const client = useQueryClient(queryClient) if (!client.getQueryState(options.queryKey)) { - client.prefetchQuery(options) + void client.fetchQuery(options).then(noop).catch(noop) } } diff --git a/packages/query-core/src/__tests__/queryClient.test-d.tsx b/packages/query-core/src/__tests__/queryClient.test-d.tsx index 4cd092ddd64..a88544075b2 100644 --- a/packages/query-core/src/__tests__/queryClient.test-d.tsx +++ b/packages/query-core/src/__tests__/queryClient.test-d.tsx @@ -1,6 +1,7 @@ import { assertType, describe, expectTypeOf, it } from 'vitest' import { queryKey } from '@tanstack/query-test-utils' import { QueryClient } from '../queryClient' +import { skipToken } from '../utils' import type { MutationFilters, QueryFilters, Updater } from '../utils' import type { Mutation } from '../mutation' import type { Query, QueryState } from '../query' @@ -11,6 +12,7 @@ import type { EnsureQueryDataOptions, FetchInfiniteQueryOptions, InfiniteData, + InfiniteQueryExecuteOptions, MutationOptions, OmitKeyof, QueryKey, @@ -158,7 +160,38 @@ describe('getQueryState', () => { }) }) +describe('fetchQuery', () => { + it('should not allow passing select option', () => { + assertType>([ + { + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + // @ts-expect-error `select` is not supported on fetchQuery options + select: (data: string) => data.length, + }, + ]) + }) +}) + describe('fetchInfiniteQuery', () => { + it('should not allow passing select option', () => { + assertType>([ + { + queryKey: ['key'], + queryFn: () => Promise.resolve({ count: 1 }), + initialPageParam: 1, + getNextPageParam: () => 2, + // @ts-expect-error `select` is not supported on fetchInfiniteQuery options + select: (data) => ({ + pages: data.pages.map( + (x: unknown) => `count: ${(x as { count: number }).count}`, + ), + pageParams: data.pageParams, + }), + }, + ]) + }) + it('should allow passing pages', async () => { const data = await new QueryClient().fetchInfiniteQuery({ queryKey: queryKey(), @@ -171,7 +204,7 @@ describe('fetchInfiniteQuery', () => { expectTypeOf(data).toEqualTypeOf>() }) - it('should not allow passing getNextPageParam without pages', () => { + it('should allow passing getNextPageParam without pages', () => { assertType>([ { queryKey: ['key'], @@ -195,6 +228,105 @@ describe('fetchInfiniteQuery', () => { }) }) +describe('query', () => { + it('should allow passing select option', () => { + const result = new QueryClient().query({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + select: (data) => data.length, + }) + + expectTypeOf(result).toEqualTypeOf>() + }) + + it('should infer select type with skipToken queryFn', () => { + const result = new QueryClient().query({ + queryKey: ['key'], + queryFn: skipToken, + select: (data: string) => data.length, + }) + + expectTypeOf(result).toEqualTypeOf>() + }) + + it('should infer select type with skipToken queryFn and enabled false', () => { + const result = new QueryClient().query({ + queryKey: ['key'], + queryFn: skipToken, + enabled: false, + select: (data: string) => data.length, + }) + + expectTypeOf(result).toEqualTypeOf>() + }) + + it('should infer select type with skipToken queryFn and enabled true', () => { + const result = new QueryClient().query({ + queryKey: ['key'], + queryFn: skipToken, + enabled: true, + select: (data: string) => data.length, + }) + + expectTypeOf(result).toEqualTypeOf>() + }) +}) + +describe('infiniteQuery', () => { + it('should allow passing select option', () => { + const result = new QueryClient().infiniteQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve({ count: 1 }), + initialPageParam: 1, + getNextPageParam: () => 2, + select: (data) => ({ + pages: data.pages.map( + (x) => `count: ${(x as { count: number }).count}`, + ), + }), + }) + + expectTypeOf(result).toEqualTypeOf }>>() + }) + + it('should allow passing pages', async () => { + const result = await new QueryClient().infiniteQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve({ count: 1 }), + getNextPageParam: () => 1, + initialPageParam: 1, + pages: 5, + }) + + expectTypeOf(result).toEqualTypeOf< + InfiniteData<{ count: number }, number> + >() + }) + + it('should allow passing getNextPageParam without pages', () => { + assertType>([ + { + queryKey: ['key'], + queryFn: () => Promise.resolve({ count: 1 }), + initialPageParam: 1, + getNextPageParam: () => 1, + }, + ]) + }) + + it('should not allow passing pages without getNextPageParam', () => { + assertType>([ + // @ts-expect-error Property 'getNextPageParam' is missing + { + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + initialPageParam: 1, + pages: 5, + }, + ]) + }) +}) + describe('defaultOptions', () => { it('should have a typed QueryFunctionContext', () => { new QueryClient({ @@ -228,12 +360,27 @@ describe('fully typed usage', () => { // Construct typed arguments // + const infiniteQueryOptions: InfiniteQueryExecuteOptions< + TData, + TError, + InfiniteData + > = { + queryKey: ['key', 'infinite'], + pages: 5, + getNextPageParam: (lastPage) => { + expectTypeOf(lastPage).toEqualTypeOf() + return 0 + }, + initialPageParam: 0, + } + const queryOptions: EnsureQueryDataOptions = { - queryKey: ['key'] as any, + queryKey: ['key', 'query'], } + const fetchInfiniteQueryOptions: FetchInfiniteQueryOptions = { - queryKey: ['key'] as any, + queryKey: ['key', 'infinite'], pages: 5, getNextPageParam: (lastPage) => { expectTypeOf(lastPage).toEqualTypeOf() @@ -241,6 +388,7 @@ describe('fully typed usage', () => { }, initialPageParam: 0, } + const mutationOptions: MutationOptions = {} const queryFilters: QueryFilters> = { @@ -311,11 +459,19 @@ describe('fully typed usage', () => { const fetchedQuery = await queryClient.fetchQuery(queryOptions) expectTypeOf(fetchedQuery).toEqualTypeOf() + const queriedData = await queryClient.query(queryOptions) + expectTypeOf(queriedData).toEqualTypeOf() + queryClient.prefetchQuery(queryOptions) - const infiniteQuery = await queryClient.fetchInfiniteQuery( + const fetchInfiniteQueryResult = await queryClient.fetchInfiniteQuery( fetchInfiniteQueryOptions, ) + expectTypeOf(fetchInfiniteQueryResult).toEqualTypeOf< + InfiniteData + >() + + const infiniteQuery = await queryClient.infiniteQuery(infiniteQueryOptions) expectTypeOf(infiniteQuery).toEqualTypeOf>() const infiniteQueryData = await queryClient.ensureInfiniteQueryData( @@ -450,9 +606,19 @@ describe('fully typed usage', () => { const fetchedQuery = await queryClient.fetchQuery(queryOptions) expectTypeOf(fetchedQuery).toEqualTypeOf() + const queriedData = await queryClient.query(queryOptions) + expectTypeOf(queriedData).toEqualTypeOf() + queryClient.prefetchQuery(queryOptions) - const infiniteQuery = await queryClient.fetchInfiniteQuery( + const fetchInfiniteQueryResult = await queryClient.fetchInfiniteQuery( + fetchInfiniteQueryOptions, + ) + expectTypeOf(fetchInfiniteQueryResult).toEqualTypeOf< + InfiniteData + >() + + const infiniteQuery = await queryClient.infiniteQuery( fetchInfiniteQueryOptions, ) expectTypeOf(infiniteQuery).toEqualTypeOf>() diff --git a/packages/query-core/src/__tests__/queryClient.test.tsx b/packages/query-core/src/__tests__/queryClient.test.tsx index 2676717fb62..e629975960a 100644 --- a/packages/query-core/src/__tests__/queryClient.test.tsx +++ b/packages/query-core/src/__tests__/queryClient.test.tsx @@ -8,11 +8,18 @@ import { dehydrate, focusManager, hydrate, + noop, onlineManager, skipToken, } from '..' import { mockOnlineManagerIsOnline } from './utils' -import type { QueryCache, QueryFunction, QueryObserverOptions } from '..' +import type { + InfiniteData, + Query, + QueryCache, + QueryFunction, + QueryObserverOptions, +} from '..' describe('queryClient', () => { let queryClient: QueryClient @@ -449,6 +456,7 @@ describe('queryClient', () => { }) }) + /** @deprecated */ describe('ensureQueryData', () => { test('should return the cached query data if the query is found', async () => { const key = queryKey() @@ -524,6 +532,100 @@ describe('queryClient', () => { }) }) + describe('query with static staleTime', () => { + test('should return the cached query data if the query is found', async () => { + const key = queryKey() + const queryFn = vi.fn(() => Promise.resolve('data')) + + queryClient.setQueryData([key, 'id'], 'bar') + + await expect( + queryClient.query({ + queryKey: [key, 'id'], + queryFn, + staleTime: 'static', + }), + ).resolves.toEqual('bar') + expect(queryFn).not.toHaveBeenCalled() + }) + + test('should return the cached query data if the query is found and cached query data is falsy', async () => { + const key = queryKey() + const queryFn = vi.fn(() => Promise.resolve(0)) + + queryClient.setQueryData([key, 'id'], null) + + await expect( + queryClient.query({ + queryKey: [key, 'id'], + queryFn, + staleTime: 'static', + }), + ).resolves.toEqual(null) + expect(queryFn).not.toHaveBeenCalled() + }) + + test('should call queryFn and return its results if the query is not found', async () => { + const key = queryKey() + const queryFn = vi.fn(() => Promise.resolve('data')) + + await expect( + queryClient.query({ + queryKey: [key], + queryFn, + staleTime: 'static', + }), + ).resolves.toEqual('data') + expect(queryFn).toHaveBeenCalledTimes(1) + }) + + test('should not fetch when initialData is provided', async () => { + const key = queryKey() + const queryFn = vi.fn(() => Promise.resolve('data')) + + await expect( + queryClient.query({ + queryKey: [key, 'id'], + queryFn, + staleTime: 'static', + initialData: 'initial', + }), + ).resolves.toEqual('initial') + + expect(queryFn).not.toHaveBeenCalled() + }) + + test('supports manual background revalidation via a second query call', async () => { + const key = queryKey() + let value = 'data-1' + const queryFn = vi.fn(() => Promise.resolve(value)) + + await expect( + queryClient.query({ + queryKey: key, + queryFn, + staleTime: 'static', + }), + ).resolves.toEqual('data-1') + expect(queryFn).toHaveBeenCalledTimes(1) + + value = 'data-2' + void queryClient + .query({ + queryKey: key, + queryFn, + staleTime: 0, + }) + .catch(noop) + + await vi.advanceTimersByTimeAsync(0) + + expect(queryFn).toHaveBeenCalledTimes(2) + expect(queryClient.getQueryData(key)).toBe('data-2') + }) + }) + + /** @deprecated */ describe('ensureInfiniteQueryData', () => { test('should return the cached query data if the query is found', async () => { const key = queryKey() @@ -584,6 +686,45 @@ describe('queryClient', () => { }) }) + describe('infiniteQuery with static staleTime', () => { + test('should return the cached query data if the query is found', async () => { + const key = queryKey() + const queryFn = vi.fn(() => Promise.resolve('data')) + + queryClient.setQueryData([key, 'id'], { + pages: ['bar'], + pageParams: [0], + }) + + await expect( + queryClient.infiniteQuery({ + queryKey: [key, 'id'], + queryFn, + staleTime: 'static', + initialPageParam: 1, + getNextPageParam: () => undefined, + }), + ).resolves.toEqual({ pages: ['bar'], pageParams: [0] }) + expect(queryFn).not.toHaveBeenCalled() + }) + + test('should fetch the query and return its results if the query is not found', async () => { + const key = queryKey() + const queryFn = vi.fn(() => Promise.resolve('data')) + + await expect( + queryClient.infiniteQuery({ + queryKey: [key, 'id'], + queryFn, + staleTime: 'static', + initialPageParam: 1, + getNextPageParam: () => undefined, + }), + ).resolves.toEqual({ pages: ['data'], pageParams: [1] }) + expect(queryFn).toHaveBeenCalledTimes(1) + }) + }) + describe('getQueriesData', () => { test('should return the query data for all matched queries', () => { const key1 = queryKey() @@ -615,6 +756,7 @@ describe('queryClient', () => { }) }) + /** @deprecated */ describe('fetchQuery', () => { test('should not type-error with strict query key', async () => { type StrictData = 'data' @@ -789,161 +931,937 @@ describe('queryClient', () => { }) }) - describe('fetchInfiniteQuery', () => { + describe('query', () => { test('should not type-error with strict query key', async () => { - type StrictData = string + type StrictData = 'data' type StrictQueryKey = ['strict', ...ReturnType] const key: StrictQueryKey = ['strict', ...queryKey()] - const data = { - pages: ['data'], - pageParams: [0], - } as const - - const fetchFn: QueryFunction = () => - Promise.resolve(data.pages[0]) + const fetchFn: QueryFunction = () => + Promise.resolve('data') await expect( - queryClient.fetchInfiniteQuery< + queryClient.query< StrictData, any, StrictData, - StrictQueryKey, - number - >({ queryKey: key, queryFn: fetchFn, initialPageParam: 0 }), - ).resolves.toEqual(data) + StrictData, + StrictQueryKey + >({ + queryKey: key, + queryFn: fetchFn, + }), + ).resolves.toEqual('data') }) - test('should return infinite query data', async () => { + // https://github.com/tannerlinsley/react-query/issues/652 + test('should not retry by default', async () => { const key = queryKey() - const result = await queryClient.fetchInfiniteQuery({ - queryKey: key, - initialPageParam: 10, - queryFn: ({ pageParam }) => Number(pageParam), - }) - const result2 = queryClient.getQueryData(key) - - const expected = { - pages: [10], - pageParams: [10], - } - expect(result).toEqual(expected) - expect(result2).toEqual(expected) + await expect( + queryClient.query({ + queryKey: key, + queryFn: (): Promise => { + throw new Error('error') + }, + }), + ).rejects.toEqual(new Error('error')) }) - }) - describe('prefetchInfiniteQuery', () => { - test('should not type-error with strict query key', async () => { - type StrictData = 'data' - type StrictQueryKey = ['strict', ...ReturnType] - const key: StrictQueryKey = ['strict', ...queryKey()] + test('should return the cached data on cache hit', async () => { + const key = queryKey() - const fetchFn: QueryFunction = () => - Promise.resolve('data') + const fetchFn = () => Promise.resolve('data') + const first = await queryClient.query({ + queryKey: key, + queryFn: fetchFn, + }) + const second = await queryClient.query({ + queryKey: key, + queryFn: fetchFn, + }) - await queryClient.prefetchInfiniteQuery< - StrictData, - any, - StrictData, - StrictQueryKey, - number - >({ queryKey: key, queryFn: fetchFn, initialPageParam: 0 }) + expect(second).toBe(first) + }) - const result = queryClient.getQueryData(key) + test('should throw when disabled and no cached data exists', async () => { + const key = queryKey() + const queryFn = vi.fn(() => Promise.resolve('data')) + const errorMsg = `Query is disabled and no cached data is available for key: '${JSON.stringify(key)}'` - expect(result).toEqual({ - pages: ['data'], - pageParams: [0], - }) + await expect( + queryClient.query({ + queryKey: key, + queryFn, + enabled: false, + }), + ).rejects.toThrowError(errorMsg) + + expect(queryFn).not.toHaveBeenCalled() }) - test('should return infinite query data', async () => { + test('should return cached data when disabled and apply select', async () => { const key = queryKey() + const queryFn = vi.fn(() => Promise.resolve('fetched-data')) - await queryClient.prefetchInfiniteQuery({ + queryClient.setQueryData(key, 'cached-data') + + const result = await queryClient.query({ queryKey: key, - queryFn: ({ pageParam }) => Number(pageParam), - initialPageParam: 10, + queryFn, + enabled: false, + staleTime: 0, + select: (data) => `${data}-selected`, }) - const result = queryClient.getQueryData(key) - - expect(result).toEqual({ - pages: [10], - pageParams: [10], - }) + expect(result).toBe('cached-data-selected') + expect(queryFn).not.toHaveBeenCalled() }) - test('should prefetch multiple pages', async () => { + test('should throw when skipToken is provided and no cached data exists', async () => { const key = queryKey() + const select = vi.fn((data: unknown) => (data as string).length) - await queryClient.prefetchInfiniteQuery({ - queryKey: key, - queryFn: ({ pageParam }) => String(pageParam), - getNextPageParam: (_lastPage, _pages, lastPageParam) => - lastPageParam + 5, - initialPageParam: 10, - pages: 3, - }) - - const result = queryClient.getQueryData(key) + await expect( + queryClient.query({ + queryKey: key, + queryFn: skipToken, + select, + }), + ).rejects.toThrowError() - expect(result).toEqual({ - pages: ['10', '15', '20'], - pageParams: [10, 15, 20], - }) + expect(select).not.toHaveBeenCalled() }) - test('should stop prefetching if getNextPageParam returns undefined', async () => { + test('should return cached data when skipToken is provided', async () => { const key = queryKey() - let count = 0 - await queryClient.prefetchInfiniteQuery({ + queryClient.setQueryData(key, 'cached-data') + + const result = await queryClient.query({ queryKey: key, - queryFn: ({ pageParam }) => String(pageParam), - getNextPageParam: (_lastPage, _pages, lastPageParam) => { - count++ - return lastPageParam >= 20 ? undefined : lastPageParam + 5 - }, - initialPageParam: 10, - pages: 5, + queryFn: skipToken, + select: (data: unknown) => (data as string).length, }) - const result = queryClient.getQueryData(key) + expect(result).toBe('cached-data'.length) + }) - expect(result).toEqual({ - pages: ['10', '15', '20'], - pageParams: [10, 15, 20], + test('should return cached data when skipToken and enabled false are both provided', async () => { + const key = queryKey() + + queryClient.setQueryData(key, { value: 'cached-data' }) + + const result = await queryClient.query({ + queryKey: key, + queryFn: skipToken, + enabled: false, + select: (data: { value: string }) => data.value.toUpperCase(), }) - // this check ensures we're exiting the fetch loop early - expect(count).toBe(3) + expect(result).toBe('CACHED-DATA') }) - }) - - describe('prefetchQuery', () => { - test('should not type-error with strict query key', async () => { - type StrictData = 'data' - type StrictQueryKey = ['strict', ...ReturnType] - const key: StrictQueryKey = ['strict', ...queryKey()] - const fetchFn: QueryFunction = () => - Promise.resolve('data') + test('should throw when enabled is true and skipToken are provided with no cached data', async () => { + await expect( + queryClient.query({ + queryKey: queryKey(), + queryFn: skipToken, + enabled: true, + }), + ).rejects.toThrowError() + }) - await queryClient.prefetchQuery< - StrictData, - any, - StrictData, - StrictQueryKey - >({ queryKey: key, queryFn: fetchFn }) + test('should return cached data when enabled is false and skipToken are provided', async () => { + const key1 = queryKey() + queryClient.setQueryData(key1, { value: 'cached-data' }) - const result = queryClient.getQueryData(key) + const booleanDisabledResult = await queryClient.query({ + queryKey: key1, + queryFn: skipToken, + enabled: false, + select: (data: { value: string }) => data.value.length, + }) - expect(result).toEqual('data') + expect(booleanDisabledResult).toBe('cached-data'.length) }) - test('should return undefined when an error is thrown', async () => { + test('should return cached data when enabled callback returns false even if queryFn would return different data', async () => { + const key = queryKey() + const queryFn = vi.fn(() => Promise.resolve('fetched-data')) + + queryClient.setQueryData(key, 'cached-data') + + const result = await queryClient.query({ + queryKey: key, + queryFn, + enabled: () => false, + }) + + expect(result).toBe('cached-data') + expect(queryFn).not.toHaveBeenCalled() + }) + + test('should fetch when enabled callback returns true and cache is stale', async () => { + const key = queryKey() + + queryClient.setQueryData(key, 'old-data') + + await vi.advanceTimersByTimeAsync(1) + + const queryFn = vi.fn(() => Promise.resolve('new-data')) + + const result = await queryClient.query({ + queryKey: key, + queryFn, + enabled: () => true, + staleTime: 0, + }) + + expect(result).toBe('new-data') + expect(queryFn).toHaveBeenCalledTimes(1) + }) + + test('should read from cache with static staleTime even if invalidated', async () => { + const key = queryKey() + + const fetchFn = vi.fn(() => Promise.resolve({ data: 'data' })) + const first = await queryClient.query({ + queryKey: key, + queryFn: fetchFn, + staleTime: 'static', + }) + + expect(first.data).toBe('data') + expect(fetchFn).toHaveBeenCalledTimes(1) + + await queryClient.invalidateQueries({ + queryKey: key, + refetchType: 'none', + }) + + const second = await queryClient.query({ + queryKey: key, + queryFn: fetchFn, + staleTime: 'static', + }) + + expect(fetchFn).toHaveBeenCalledTimes(1) + + expect(second).toBe(first) + }) + + test('should be able to fetch when garbage collection time is set to 0 and then be removed', async () => { + const key1 = queryKey() + const promise = queryClient.query({ + queryKey: key1, + queryFn: () => sleep(10).then(() => 1), + gcTime: 0, + }) + await vi.advanceTimersByTimeAsync(10) + await expect(promise).resolves.toEqual(1) + await vi.advanceTimersByTimeAsync(1) + expect(queryClient.getQueryData(key1)).toEqual(undefined) + }) + + test('should keep a query in cache if garbage collection time is Infinity', async () => { + const key1 = queryKey() + const promise = queryClient.query({ + queryKey: key1, + queryFn: () => sleep(10).then(() => 1), + gcTime: Infinity, + }) + await vi.advanceTimersByTimeAsync(10) + const result2 = queryClient.getQueryData(key1) + await expect(promise).resolves.toEqual(1) + expect(result2).toEqual(1) + }) + + test('should not force fetch', async () => { + const key = queryKey() + + queryClient.setQueryData(key, 'og') + const fetchFn = () => Promise.resolve('new') + const first = await queryClient.query({ + queryKey: key, + queryFn: fetchFn, + initialData: 'initial', + staleTime: 100, + }) + expect(first).toBe('og') + }) + + test('should only fetch if the data is older then the given stale time', async () => { + const key = queryKey() + + let count = 0 + const queryFn = () => ++count + + queryClient.setQueryData(key, count) + const firstPromise = queryClient.query({ + queryKey: key, + queryFn, + staleTime: 100, + }) + await expect(firstPromise).resolves.toBe(0) + await vi.advanceTimersByTimeAsync(10) + const secondPromise = queryClient.query({ + queryKey: key, + queryFn, + staleTime: 10, + }) + await expect(secondPromise).resolves.toBe(1) + const thirdPromise = queryClient.query({ + queryKey: key, + queryFn, + staleTime: 10, + }) + await expect(thirdPromise).resolves.toBe(1) + await vi.advanceTimersByTimeAsync(10) + const fourthPromise = queryClient.query({ + queryKey: key, + queryFn, + staleTime: 10, + }) + await expect(fourthPromise).resolves.toBe(2) + }) + + test('should evaluate staleTime when provided as a function', async () => { + const key = queryKey() + const staleTime = vi.fn(() => 0) + + queryClient.setQueryData(key, 'old-data') + + await vi.advanceTimersByTimeAsync(1) + + const queryFn = vi.fn(() => Promise.resolve('new-data')) + + const result = await queryClient.query({ + queryKey: key, + queryFn, + staleTime, + }) + + expect(result).toBe('new-data') + expect(queryFn).toHaveBeenCalledTimes(1) + expect(staleTime).toHaveBeenCalledTimes(1) + }) + + test('should allow new meta', async () => { + const key = queryKey() + + const first = await queryClient.query({ + queryKey: key, + queryFn: ({ meta }) => Promise.resolve(meta), + meta: { + foo: true, + }, + }) + expect(first).toStrictEqual({ foo: true }) + + const second = await queryClient.query({ + queryKey: key, + queryFn: ({ meta }) => Promise.resolve(meta), + meta: { + foo: false, + }, + }) + expect(second).toStrictEqual({ foo: false }) + }) + + test('should fetch when enabled is true and cache is stale', async () => { + const key = queryKey() + + queryClient.setQueryData(key, 'old-data') + + await vi.advanceTimersByTimeAsync(1) + + const queryFn = vi.fn(() => Promise.resolve('new-data')) + + const result = await queryClient.query({ + queryKey: key, + queryFn, + enabled: true, + staleTime: 0, + }) + + expect(result).toBe('new-data') + expect(queryFn).toHaveBeenCalledTimes(1) + }) + + test('should propagate errors', async () => { + const key = queryKey() + + await expect( + queryClient.query({ + queryKey: key, + queryFn: (): Promise => { + throw new Error('error') + }, + }), + ).rejects.toEqual(new Error('error')) + }) + + test('should apply select when data is fresh in cache', async () => { + const key = queryKey() + const queryFn = vi.fn(() => Promise.resolve('fetched-data')) + + queryClient.setQueryData(key, 'cached-data') + + const result = await queryClient.query({ + queryKey: key, + queryFn, + staleTime: Infinity, + select: (data) => `${data}-selected`, + }) + + expect(result).toBe('cached-data-selected') + expect(queryFn).not.toHaveBeenCalled() + }) + + test('should apply select to freshly fetched data', async () => { + const key = queryKey() + const queryFn = vi.fn(() => Promise.resolve({ value: 'fetched-data' })) + + const result = await queryClient.query({ + queryKey: key, + queryFn, + select: (data) => data.value.toUpperCase(), + }) + + expect(result).toBe('FETCHED-DATA') + expect(queryFn).toHaveBeenCalledTimes(1) + }) + }) + + /** @deprecated */ + describe('fetchInfiniteQuery', () => { + test('should not type-error with strict query key', async () => { + type StrictData = string + type StrictQueryKey = ['strict', ...ReturnType] + const key: StrictQueryKey = ['strict', ...queryKey()] + + const data = { + pages: ['data'], + pageParams: [0], + } as const + + const fetchFn: QueryFunction = () => + Promise.resolve(data.pages[0]) + + await expect( + queryClient.fetchInfiniteQuery< + StrictData, + any, + StrictData, + StrictQueryKey, + number + >({ queryKey: key, queryFn: fetchFn, initialPageParam: 0 }), + ).resolves.toEqual(data) + }) + + test('should return infinite query data', async () => { + const key = queryKey() + const result = await queryClient.fetchInfiniteQuery({ + queryKey: key, + initialPageParam: 10, + queryFn: ({ pageParam }) => Number(pageParam), + }) + const result2 = queryClient.getQueryData(key) + + const expected = { + pages: [10], + pageParams: [10], + } + + expect(result).toEqual(expected) + expect(result2).toEqual(expected) + }) + }) + + describe('infiniteQuery', () => { + test('should not type-error with strict query key', async () => { + type StrictData = string + type StrictQueryKey = ['strict', ...ReturnType] + const key: StrictQueryKey = ['strict', ...queryKey()] + + const data = { + pages: ['data'], + pageParams: [0], + } as const + + const fetchFn: QueryFunction = () => + Promise.resolve(data.pages[0]) + + await expect( + queryClient.infiniteQuery< + StrictData, + any, + StrictData, + StrictQueryKey, + number + >({ queryKey: key, queryFn: fetchFn, initialPageParam: 0 }), + ).resolves.toEqual(data) + }) + + test('should return infinite query data', async () => { + const key = queryKey() + const result = await queryClient.infiniteQuery({ + queryKey: key, + initialPageParam: 10, + queryFn: ({ pageParam }) => Number(pageParam), + }) + const result2 = queryClient.getQueryData(key) + + const expected = { + pages: [10], + pageParams: [10], + } + + expect(result).toEqual(expected) + expect(result2).toEqual(expected) + }) + + test('should throw when disabled and no cached data exists', async () => { + const key = queryKey() + const queryFn = vi.fn(({ pageParam }: { pageParam: number }) => + Promise.resolve(pageParam), + ) + const errorMsg = `Query is disabled and no cached data is available for key: '${JSON.stringify(key)}'` + + await expect( + queryClient.infiniteQuery({ + queryKey: key, + queryFn, + initialPageParam: 0, + enabled: false, + }), + ).rejects.toThrow(errorMsg) + + expect(queryFn).not.toHaveBeenCalled() + }) + + test('should return cached data when disabled and apply select', async () => { + const key = queryKey() + const queryFn = vi.fn(({ pageParam }: { pageParam: number }) => + Promise.resolve(`'fetched-${String(pageParam)}`), + ) + + queryClient.setQueryData(key, { + pages: ['cached-page'], + pageParams: [0], + }) + + const result = await queryClient.infiniteQuery({ + queryKey: key, + queryFn, + initialPageParam: 0, + enabled: false, + select: (data) => data.pages.map((page) => `${page}-selected`), + }) + + expect(result).toEqual(['cached-page-selected']) + expect(queryFn).not.toHaveBeenCalled() + }) + + test('should return cached data when skipToken is provided', async () => { + const key = queryKey() + + queryClient.setQueryData(key, { + pages: ['page-1'], + pageParams: [0], + }) + + const result = await queryClient.infiniteQuery({ + queryKey: key, + queryFn: skipToken, + initialPageParam: 0, + }) + + expect(result).toEqual({ + pages: ['page-1'], + pageParams: [0], + }) + }) + + test('should throw when skipToken is provided and no cached data exists', async () => { + const key = queryKey() + const select = vi.fn( + (data: { pages: Array }) => data.pages.length, + ) + + await expect( + queryClient.infiniteQuery({ + queryKey: key, + queryFn: skipToken, + initialPageParam: 0, + select, + }), + ).rejects.toThrowError() + + expect(select).not.toHaveBeenCalled() + }) + + test('should throw when enabled is true and skipToken are provided with no cached data', async () => { + await expect( + queryClient.infiniteQuery({ + queryKey: queryKey(), + queryFn: skipToken, + initialPageParam: 0, + enabled: true, + }), + ).rejects.toThrowError() + }) + + test('should return cached data when enabled resolves false and skipToken are provided', async () => { + const key = queryKey() + + queryClient.setQueryData(key, { + pages: [{ value: 'cached-page' }], + pageParams: [0], + }) + + const result = await queryClient.infiniteQuery({ + queryKey: key, + queryFn: skipToken, + initialPageParam: 0, + enabled: () => false, + select: (data: { pages: Array<{ value: string }> }) => + data.pages[0]?.value.length, + }) + + expect(result).toBe('cached-page'.length) + }) + + test('should fetch when enabled callback returns true and cache is stale', async () => { + const key = queryKey() + + queryClient.setQueryData(key, { + pages: ['old-page'], + pageParams: [0], + }) + + await vi.advanceTimersByTimeAsync(1) + + const queryFn = vi.fn(({ pageParam }: { pageParam: number }) => + Promise.resolve(`new-page-${String(pageParam)}`), + ) + + const result = await queryClient.infiniteQuery({ + queryKey: key, + queryFn, + initialPageParam: 0, + enabled: () => true, + staleTime: 0, + }) + + expect(result).toEqual({ + pages: ['new-page-0'], + pageParams: [0], + }) + expect(queryFn).toHaveBeenCalledTimes(1) + }) + + test('should evaluate staleTime callback and refetch when it returns stale', async () => { + const key = queryKey() + + queryClient.setQueryData(key, { + pages: [{ value: 'old-page', staleTime: 0 }], + pageParams: [0], + }) + + await vi.advanceTimersByTimeAsync(1) + + const queryFn = vi.fn(({ pageParam }: { pageParam: number }) => + Promise.resolve({ + value: `new-page-${String(pageParam)}`, + staleTime: 0, + }), + ) + const staleTimeSpy = vi.fn() + const staleTime = ( + query: Query< + { value: string; staleTime: number }, + Error, + InfiniteData<{ value: string; staleTime: number }, number> + >, + ) => { + staleTimeSpy() + return query.state.data?.pages[0]?.staleTime ?? 0 + } + + const result = await queryClient.infiniteQuery({ + queryKey: key, + queryFn, + initialPageParam: 0, + staleTime, + }) + + expect(result).toEqual({ + pages: [{ value: 'new-page-0', staleTime: 0 }], + pageParams: [0], + }) + expect(staleTimeSpy).toHaveBeenCalled() + expect(queryFn).toHaveBeenCalledTimes(1) + }) + + test('should read from cache with static staleTime even if invalidated', async () => { + const key = queryKey() + + const queryFn = vi.fn(({ pageParam }: { pageParam: number }) => + Promise.resolve({ value: `fetched-${String(pageParam)}` }), + ) + const first = await queryClient.infiniteQuery({ + queryKey: key, + queryFn, + initialPageParam: 0, + staleTime: 'static', + }) + + expect(first).toEqual({ + pages: [{ value: 'fetched-0' }], + pageParams: [0], + }) + expect(queryFn).toHaveBeenCalledTimes(1) + + await queryClient.invalidateQueries({ + queryKey: key, + refetchType: 'none', + }) + + const second = await queryClient.infiniteQuery({ + queryKey: key, + queryFn, + initialPageParam: 0, + staleTime: 'static', + }) + + expect(queryFn).toHaveBeenCalledTimes(1) + expect(second).toBe(first) + }) + + test('should apply select to infinite query data', async () => { + const key = queryKey() + + const result = await queryClient.infiniteQuery({ + queryKey: key, + initialPageParam: 10, + queryFn: ({ pageParam }) => Number(pageParam), + select: (data) => data.pages.map((page) => page * 2), + }) + + expect(result).toEqual([20]) + }) + }) + + /** @deprecated */ + describe('prefetchInfiniteQuery', () => { + test('should not type-error with strict query key', async () => { + type StrictData = 'data' + type StrictQueryKey = ['strict', ...ReturnType] + const key: StrictQueryKey = ['strict', ...queryKey()] + + const fetchFn: QueryFunction = () => + Promise.resolve('data') + + await queryClient.prefetchInfiniteQuery< + StrictData, + any, + StrictData, + StrictQueryKey, + number + >({ queryKey: key, queryFn: fetchFn, initialPageParam: 0 }) + + const result = queryClient.getQueryData(key) + + expect(result).toEqual({ + pages: ['data'], + pageParams: [0], + }) + }) + + test('should return infinite query data', async () => { + const key = queryKey() + + await queryClient.prefetchInfiniteQuery({ + queryKey: key, + queryFn: ({ pageParam }) => Number(pageParam), + initialPageParam: 10, + }) + + const result = queryClient.getQueryData(key) + + expect(result).toEqual({ + pages: [10], + pageParams: [10], + }) + }) + + test('should prefetch multiple pages', async () => { + const key = queryKey() + + await queryClient.prefetchInfiniteQuery({ + queryKey: key, + queryFn: ({ pageParam }) => String(pageParam), + getNextPageParam: (_lastPage, _pages, lastPageParam) => + lastPageParam + 5, + initialPageParam: 10, + pages: 3, + }) + + const result = queryClient.getQueryData(key) + + expect(result).toEqual({ + pages: ['10', '15', '20'], + pageParams: [10, 15, 20], + }) + }) + + test('should stop prefetching if getNextPageParam returns undefined', async () => { + const key = queryKey() + let count = 0 + + await queryClient.prefetchInfiniteQuery({ + queryKey: key, + queryFn: ({ pageParam }) => String(pageParam), + getNextPageParam: (_lastPage, _pages, lastPageParam) => { + count++ + return lastPageParam >= 20 ? undefined : lastPageParam + 5 + }, + initialPageParam: 10, + pages: 5, + }) + + const result = queryClient.getQueryData(key) + + expect(result).toEqual({ + pages: ['10', '15', '20'], + pageParams: [10, 15, 20], + }) + + // this check ensures we're exiting the fetch loop early + expect(count).toBe(3) + }) + }) + + describe('infiniteQuery used for prefetching', () => { + test('should not type-error with strict query key', async () => { + type StrictData = 'data' + type StrictQueryKey = ['strict', ...ReturnType] + const key: StrictQueryKey = ['strict', ...queryKey()] + + const fetchFn: QueryFunction = () => + Promise.resolve('data') + + await queryClient + .infiniteQuery({ + queryKey: key, + queryFn: fetchFn, + initialPageParam: 0, + }) + .catch(noop) + + const result = queryClient.getQueryData(key) + + expect(result).toEqual({ + pages: ['data'], + pageParams: [0], + }) + }) + + test('should return infinite query data', async () => { + const key = queryKey() + + await queryClient + .infiniteQuery({ + queryKey: key, + queryFn: ({ pageParam }) => Number(pageParam), + initialPageParam: 10, + }) + .catch(noop) + + const result = queryClient.getQueryData(key) + + expect(result).toEqual({ + pages: [10], + pageParams: [10], + }) + }) + + test('should prefetch multiple pages', async () => { + const key = queryKey() + + await queryClient + .infiniteQuery({ + queryKey: key, + queryFn: ({ pageParam }) => String(pageParam), + getNextPageParam: (_lastPage, _pages, lastPageParam) => + lastPageParam + 5, + initialPageParam: 10, + pages: 3, + }) + .catch(noop) + + const result = queryClient.getQueryData(key) + + expect(result).toEqual({ + pages: ['10', '15', '20'], + pageParams: [10, 15, 20], + }) + }) + + test('should stop prefetching if getNextPageParam returns undefined', async () => { + const key = queryKey() + let count = 0 + + await queryClient + .infiniteQuery({ + queryKey: key, + queryFn: ({ pageParam }) => String(pageParam), + getNextPageParam: (_lastPage, _pages, lastPageParam) => { + count++ + return lastPageParam >= 20 ? undefined : lastPageParam + 5 + }, + initialPageParam: 10, + pages: 5, + }) + .catch(noop) + + const result = queryClient.getQueryData(key) + + expect(result).toEqual({ + pages: ['10', '15', '20'], + pageParams: [10, 15, 20], + }) + + // this check ensures we're exiting the fetch loop early + expect(count).toBe(3) + }) + }) + + /** @deprecated */ + describe('prefetchQuery', () => { + test('should not type-error with strict query key', async () => { + type StrictData = 'data' + type StrictQueryKey = ['strict', ...ReturnType] + const key: StrictQueryKey = ['strict', ...queryKey()] + + const fetchFn: QueryFunction = () => + Promise.resolve('data') + + await queryClient.prefetchQuery< + StrictData, + any, + StrictData, + StrictQueryKey + >({ queryKey: key, queryFn: fetchFn }) + + const result = queryClient.getQueryData(key) + + expect(result).toEqual('data') + }) + + test('should return undefined when an error is thrown', async () => { const key = queryKey() const result = await queryClient.prefetchQuery({ @@ -971,6 +1889,59 @@ describe('queryClient', () => { }) }) + describe('query used for prefetching', () => { + test('should not type-error with strict query key', async () => { + type StrictData = 'data' + type StrictQueryKey = ['strict', ...ReturnType] + const key: StrictQueryKey = ['strict', ...queryKey()] + + const fetchFn: QueryFunction = () => + Promise.resolve('data') + + await queryClient + .query({ + queryKey: key, + queryFn: fetchFn, + }) + .catch(noop) + + const result = queryClient.getQueryData(key) + + expect(result).toEqual('data') + }) + + test('should resolve to undefined when error is caught with noop', async () => { + const key = queryKey() + + const result = await queryClient + .query({ + queryKey: key, + queryFn: (): Promise => { + throw new Error('error') + }, + retry: false, + }) + .catch(noop) + + expect(result).toBeUndefined() + }) + + test('should be garbage collected after gcTime if unused', async () => { + const key = queryKey() + + await queryClient + .query({ + queryKey: key, + queryFn: () => 'data', + gcTime: 10, + }) + .catch(noop) + expect(queryCache.find({ queryKey: key })).toBeDefined() + await vi.advanceTimersByTimeAsync(15) + expect(queryCache.find({ queryKey: key })).not.toBeDefined() + }) + }) + describe('removeQueries', () => { test('should not crash when exact is provided', async () => { const key = queryKey() diff --git a/packages/query-core/src/queryClient.ts b/packages/query-core/src/queryClient.ts index 80cc36668aa..fe1f7f92b2b 100644 --- a/packages/query-core/src/queryClient.ts +++ b/packages/query-core/src/queryClient.ts @@ -4,6 +4,7 @@ import { hashQueryKeyByOptions, noop, partialMatchKey, + resolveEnabled, resolveStaleTime, skipToken, } from './utils' @@ -25,6 +26,7 @@ import type { InferDataFromTag, InferErrorFromTag, InfiniteData, + InfiniteQueryExecuteOptions, InvalidateOptions, InvalidateQueryFilters, MutationKey, @@ -33,6 +35,7 @@ import type { NoInfer, OmitKeyof, QueryClientConfig, + QueryExecuteOptions, QueryKey, QueryObserverOptions, QueryOptions, @@ -137,6 +140,9 @@ export class QueryClient { .data } + /** + * @deprecated Use queryClient.query({ ...options, staleTime: 'static' }) instead. This method will be removed in the next major version. + */ ensureQueryData< TQueryFnData, TError = DefaultError, @@ -338,6 +344,62 @@ export class QueryClient { return Promise.all(promises).then(noop) } + async query< + TQueryFnData, + TError = DefaultError, + TData = TQueryFnData, + TQueryData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = never, + >( + options: QueryExecuteOptions< + TQueryFnData, + TError, + TData, + TQueryData, + TQueryKey, + TPageParam + >, + ): Promise { + const defaultedOptions = this.defaultQueryOptions(options) + + // https://github.com/tannerlinsley/react-query/issues/652 + if (defaultedOptions.retry === undefined) { + defaultedOptions.retry = false + } + + const query = this.#queryCache.build(this, defaultedOptions) + const isEnabled = resolveEnabled(defaultedOptions.enabled, query) !== false + + if (!isEnabled && query.state.data === undefined) { + return Promise.reject( + new Error( + `Query is disabled and no cached data is available for key: '${defaultedOptions.queryHash}'`, + ), + ) + } + + const isStale = query.isStaleByTime( + resolveStaleTime(defaultedOptions.staleTime, query), + ) + + const queryData = + isStale && isEnabled + ? await query.fetch(defaultedOptions) + : (query.state.data as TQueryData) + + const select = defaultedOptions.select + + if (select) { + return select(queryData) + } + + return queryData as unknown as TData + } + + /** + * @deprecated Use queryClient.query(options) instead. This method will be removed in the next major version. + */ fetchQuery< TQueryFnData, TError = DefaultError, @@ -369,6 +431,9 @@ export class QueryClient { : Promise.resolve(query.state.data as TData) } + /** + * @deprecated Use queryClient.query(options) instead. You can swallow errors with `.catch(noop)`. This method will be removed in the next major version. + */ prefetchQuery< TQueryFnData = unknown, TError = DefaultError, @@ -380,6 +445,37 @@ export class QueryClient { return this.fetchQuery(options).then(noop).catch(noop) } + infiniteQuery< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, + >( + options: InfiniteQueryExecuteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + >, + ): Promise< + Array extends Array> + ? InfiniteData + : TData + > { + options.behavior = infiniteQueryBehavior< + TQueryFnData, + TError, + TQueryFnData, + TPageParam + >(options.pages) + return this.query(options as any) + } + + /** + * @deprecated Use queryClient.infiniteQuery(options) instead. This method will be removed in the next major version. + */ fetchInfiniteQuery< TQueryFnData, TError = DefaultError, @@ -404,6 +500,9 @@ export class QueryClient { return this.fetchQuery(options as any) } + /** + * @deprecated Use queryClient.infiniteQuery(options) instead. You can swallow errors with `.catch(noop)`. This method will be removed in the next major version. + */ prefetchInfiniteQuery< TQueryFnData, TError = DefaultError, @@ -422,6 +521,9 @@ export class QueryClient { return this.fetchInfiniteQuery(options).then(noop).catch(noop) } + /** + * @deprecated Use queryClient.infiniteQuery({ ...options, staleTime: 'static' }) instead. This method will be removed in the next major version. + */ ensureInfiniteQueryData< TQueryFnData, TError = DefaultError, diff --git a/packages/query-core/src/types.ts b/packages/query-core/src/types.ts index 4f3f4caed20..9a1f9b5b3d1 100644 --- a/packages/query-core/src/types.ts +++ b/packages/query-core/src/types.ts @@ -490,6 +490,32 @@ export type DefaultedInfiniteQueryObserverOptions< 'throwOnError' | 'refetchOnReconnect' | 'queryHash' > +export interface QueryExecuteOptions< + TQueryFnData = unknown, + TError = DefaultError, + TData = TQueryFnData, + TQueryData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = never, +> extends WithRequired< + QueryOptions, + 'queryKey' +> { + initialPageParam?: never + /** + * Set this to `false` or a function that returns `false` to disable fetching. + * If cached data exists, it will be returned. + */ + enabled?: Enabled + select?: (data: TQueryData) => TData + /** + * The time in milliseconds after data is considered stale. + * If the data is fresh it will be returned from the cache. + */ + staleTime?: StaleTimeFunction +} + +/** @deprecated */ export interface FetchQueryOptions< TQueryFnData = unknown, TError = DefaultError, @@ -508,6 +534,7 @@ export interface FetchQueryOptions< staleTime?: StaleTimeFunction } +/** @deprecated */ export interface EnsureQueryDataOptions< TQueryFnData = unknown, TError = DefaultError, @@ -524,6 +551,7 @@ export interface EnsureQueryDataOptions< revalidateIfStale?: boolean } +/** @deprecated */ export type EnsureInfiniteQueryDataOptions< TQueryFnData = unknown, TError = DefaultError, @@ -540,13 +568,34 @@ export type EnsureInfiniteQueryDataOptions< revalidateIfStale?: boolean } -type FetchInfiniteQueryPages = +type InfiniteQueryPages = | { pages?: never } | { pages: number getNextPageParam: GetNextPageParamFunction } +export type InfiniteQueryExecuteOptions< + TQueryFnData = unknown, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, +> = Omit< + QueryExecuteOptions< + TQueryFnData, + TError, + TData, + InfiniteData, + TQueryKey, + TPageParam + >, + 'initialPageParam' +> & + InitialPageParam & + InfiniteQueryPages + +/** @deprecated */ export type FetchInfiniteQueryOptions< TQueryFnData = unknown, TError = DefaultError, @@ -564,7 +613,7 @@ export type FetchInfiniteQueryOptions< 'initialPageParam' > & InitialPageParam & - FetchInfiniteQueryPages + InfiniteQueryPages export interface ResultOptions { throwOnError?: boolean diff --git a/packages/react-query/src/__tests__/infiniteQueryOptions.test-d.tsx b/packages/react-query/src/__tests__/infiniteQueryOptions.test-d.tsx index aaa38f4aa37..516a441a7b9 100644 --- a/packages/react-query/src/__tests__/infiniteQueryOptions.test-d.tsx +++ b/packages/react-query/src/__tests__/infiniteQueryOptions.test-d.tsx @@ -51,6 +51,20 @@ describe('infiniteQueryOptions', () => { InfiniteData | undefined >() }) + it('should work when passed to useInfiniteQuery with select', () => { + const options = infiniteQueryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + getNextPageParam: () => 1, + initialPageParam: 1, + select: (data) => data.pages, + }) + + const { data } = useInfiniteQuery(options) + + // known issue: type of pageParams is unknown when returned from useInfiniteQuery + expectTypeOf(data).toEqualTypeOf | undefined>() + }) it('should work when passed to useSuspenseInfiniteQuery', () => { const options = infiniteQueryOptions({ queryKey: queryKey(), @@ -63,6 +77,44 @@ describe('infiniteQueryOptions', () => { expectTypeOf(data).toEqualTypeOf>() }) + it('should work when passed to useSuspenseInfiniteQuery with select', () => { + const options = infiniteQueryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + getNextPageParam: () => 1, + initialPageParam: 1, + select: (data) => data.pages, + }) + + const { data } = useSuspenseInfiniteQuery(options) + + expectTypeOf(data).toEqualTypeOf>() + }) + it('should work when passed to infiniteQuery', async () => { + const options = infiniteQueryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + getNextPageParam: () => 1, + initialPageParam: 1, + }) + + const data = await new QueryClient().infiniteQuery(options) + + expectTypeOf(data).toEqualTypeOf>() + }) + it('should work when passed to infiniteQuery with select', async () => { + const options = infiniteQueryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + getNextPageParam: () => 1, + initialPageParam: 1, + select: (data) => data.pages, + }) + + const data = await new QueryClient().infiniteQuery(options) + + expectTypeOf(data).toEqualTypeOf>() + }) it('should work when passed to fetchInfiniteQuery', async () => { const options = infiniteQueryOptions({ queryKey: queryKey(), @@ -75,6 +127,19 @@ describe('infiniteQueryOptions', () => { expectTypeOf(data).toEqualTypeOf>() }) + it('should ignore select when passed to fetchInfiniteQuery', async () => { + const options = infiniteQueryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + getNextPageParam: () => 1, + initialPageParam: 1, + select: (data) => data.pages, + }) + + const data = await new QueryClient().fetchInfiniteQuery(options) + + expectTypeOf(data).toEqualTypeOf>() + }) it('should tag the queryKey with the result type of the QueryFn', () => { const { queryKey: tagged } = infiniteQueryOptions({ queryKey: queryKey(), diff --git a/packages/react-query/src/__tests__/queryOptions.test-d.tsx b/packages/react-query/src/__tests__/queryOptions.test-d.tsx index dcfaeab6589..17f8b3c3ab7 100644 --- a/packages/react-query/src/__tests__/queryOptions.test-d.tsx +++ b/packages/react-query/src/__tests__/queryOptions.test-d.tsx @@ -56,7 +56,15 @@ describe('queryOptions', () => { const { data } = useSuspenseQuery(options) expectTypeOf(data).toEqualTypeOf() }) + it('should work when passed to query', async () => { + const options = queryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + }) + const data = await new QueryClient().query(options) + expectTypeOf(data).toEqualTypeOf() + }) it('should work when passed to fetchQuery', async () => { const options = queryOptions({ queryKey: queryKey(), @@ -66,6 +74,26 @@ describe('queryOptions', () => { const data = await new QueryClient().fetchQuery(options) expectTypeOf(data).toEqualTypeOf() }) + it('should work when passed to query with select', async () => { + const options = queryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + select: (data) => data.toString(), + }) + + const data = await new QueryClient().query(options) + expectTypeOf(data).toEqualTypeOf() + }) + it('should ignore select when passed to fetchQuery', async () => { + const options = queryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + select: (data) => data.toString(), + }) + + const data = await new QueryClient().fetchQuery(options) + expectTypeOf(data).toEqualTypeOf() + }) it('should work when passed to useQueries', () => { const options = queryOptions({ queryKey: queryKey(), diff --git a/packages/react-query/src/__tests__/useInfiniteQuery.test-d.tsx b/packages/react-query/src/__tests__/useInfiniteQuery.test-d.tsx index b4784aa4b09..3c8d7f8301f 100644 --- a/packages/react-query/src/__tests__/useInfiniteQuery.test-d.tsx +++ b/packages/react-query/src/__tests__/useInfiniteQuery.test-d.tsx @@ -38,6 +38,18 @@ describe('pageParam', () => { }) }) + it('initialPageParam should define type of param passed to queryFunctionContext for infiniteQuery', () => { + const queryClient = new QueryClient() + queryClient.infiniteQuery({ + queryKey: ['key'], + queryFn: ({ pageParam }) => { + expectTypeOf(pageParam).toEqualTypeOf() + return Promise.resolve(pageParam) + }, + initialPageParam: 1, + }) + }) + it('initialPageParam should define type of param passed to queryFunctionContext for prefetchInfiniteQuery', () => { const queryClient = new QueryClient() queryClient.prefetchInfiniteQuery({ diff --git a/packages/react-query/src/__tests__/usePrefetchInfiniteQuery.test-d.tsx b/packages/react-query/src/__tests__/usePrefetchInfiniteQuery.test-d.tsx index 231c47cedb3..79fd2933bd9 100644 --- a/packages/react-query/src/__tests__/usePrefetchInfiniteQuery.test-d.tsx +++ b/packages/react-query/src/__tests__/usePrefetchInfiniteQuery.test-d.tsx @@ -24,7 +24,7 @@ describe('usePrefetchInfiniteQuery', () => { ) }) - it('should not allow refetchInterval, enabled or throwOnError options', () => { + it('should not allow refetchInterval or throwOnError options', () => { assertType( usePrefetchInfiniteQuery({ queryKey: queryKey(), @@ -36,17 +36,6 @@ describe('usePrefetchInfiniteQuery', () => { }), ) - assertType( - usePrefetchInfiniteQuery({ - queryKey: queryKey(), - queryFn: () => Promise.resolve(5), - initialPageParam: 1, - getNextPageParam: () => 1, - // @ts-expect-error TS2353 - enabled: true, - }), - ) - assertType( usePrefetchInfiniteQuery({ queryKey: queryKey(), diff --git a/packages/react-query/src/__tests__/usePrefetchQuery.test-d.tsx b/packages/react-query/src/__tests__/usePrefetchQuery.test-d.tsx index d5c1d7c2359..4ce10ac1c1b 100644 --- a/packages/react-query/src/__tests__/usePrefetchQuery.test-d.tsx +++ b/packages/react-query/src/__tests__/usePrefetchQuery.test-d.tsx @@ -1,6 +1,6 @@ import { assertType, describe, expectTypeOf, it } from 'vitest' import { queryKey } from '@tanstack/query-test-utils' -import { skipToken, usePrefetchQuery } from '..' +import { usePrefetchQuery } from '..' describe('usePrefetchQuery', () => { it('should return nothing', () => { @@ -12,7 +12,7 @@ describe('usePrefetchQuery', () => { expectTypeOf(result).toEqualTypeOf() }) - it('should not allow refetchInterval, enabled or throwOnError options', () => { + it('should not allow refetchInterval, or throwOnError options', () => { assertType( usePrefetchQuery({ queryKey: queryKey(), @@ -22,15 +22,6 @@ describe('usePrefetchQuery', () => { }), ) - assertType( - usePrefetchQuery({ - queryKey: queryKey(), - queryFn: () => Promise.resolve(5), - // @ts-expect-error TS2345 - enabled: true, - }), - ) - assertType( usePrefetchQuery({ queryKey: queryKey(), @@ -40,21 +31,4 @@ describe('usePrefetchQuery', () => { }), ) }) - - it('should not allow skipToken in queryFn', () => { - assertType( - usePrefetchQuery({ - queryKey: queryKey(), - // @ts-expect-error - queryFn: skipToken, - }), - ) - assertType( - usePrefetchQuery({ - queryKey: queryKey(), - // @ts-expect-error - queryFn: Math.random() > 0.5 ? skipToken : () => Promise.resolve(5), - }), - ) - }) }) diff --git a/packages/react-query/src/__tests__/useQuery.promise.test.tsx b/packages/react-query/src/__tests__/useQuery.promise.test.tsx index 5e1d892df04..dd274a8cb82 100644 --- a/packages/react-query/src/__tests__/useQuery.promise.test.tsx +++ b/packages/react-query/src/__tests__/useQuery.promise.test.tsx @@ -753,6 +753,62 @@ describe('useQuery().promise', { timeout: 10_000 }, () => { expect(queryFn).toHaveBeenCalledOnce() }) + it('should dedupe when re-fetched with queryClient.query while suspending', async () => { + const key = queryKey() + const renderStream = createRenderStream({ snapshotDOM: true }) + const queryFn = vi.fn().mockImplementation(async () => { + await vi.advanceTimersByTimeAsync(10) + return 'test' + }) + + const options = { + queryKey: key, + queryFn, + } + + function MyComponent(props: { promise: Promise }) { + const data = React.use(props.promise) + + return <>{data} + } + + function Loading() { + return <>loading.. + } + function Page() { + const query = useQuery(options) + + return ( +
+ }> + + + +
+ ) + } + + const rendered = await renderStream.render( + + + , + ) + + { + const { withinDOM } = await renderStream.takeRender() + withinDOM().getByText('loading..') + } + + rendered.getByText('fetch').click() + + { + const { withinDOM } = await renderStream.takeRender() + withinDOM().getByText('test') + } + + expect(queryFn).toHaveBeenCalledOnce() + }) + it('should dedupe when re-fetched with refetchQueries while suspending', async () => { const key = queryKey() let count = 0 diff --git a/packages/react-query/src/types.ts b/packages/react-query/src/types.ts index 50df2d333f2..18bc15f996a 100644 --- a/packages/react-query/src/types.ts +++ b/packages/react-query/src/types.ts @@ -5,7 +5,6 @@ import type { DefinedInfiniteQueryObserverResult, DefinedQueryObserverResult, DistributiveOmit, - FetchQueryOptions, InfiniteQueryObserverOptions, InfiniteQueryObserverResult, MutateFunction, @@ -46,21 +45,6 @@ export interface UseBaseQueryOptions< subscribed?: boolean } -export interface UsePrefetchQueryOptions< - TQueryFnData = unknown, - TError = DefaultError, - TData = TQueryFnData, - TQueryKey extends QueryKey = QueryKey, -> extends OmitKeyof< - FetchQueryOptions, - 'queryFn' -> { - queryFn?: Exclude< - FetchQueryOptions['queryFn'], - SkipToken - > -} - export type AnyUseQueryOptions = UseQueryOptions export interface UseQueryOptions< TQueryFnData = unknown, diff --git a/packages/react-query/src/usePrefetchInfiniteQuery.tsx b/packages/react-query/src/usePrefetchInfiniteQuery.tsx index 08c2fcdfa3e..b49e2630562 100644 --- a/packages/react-query/src/usePrefetchInfiniteQuery.tsx +++ b/packages/react-query/src/usePrefetchInfiniteQuery.tsx @@ -1,7 +1,9 @@ +import { noop } from '@tanstack/query-core' import { useQueryClient } from './QueryClientProvider' + import type { DefaultError, - FetchInfiniteQueryOptions, + InfiniteQueryExecuteOptions, QueryClient, QueryKey, } from '@tanstack/query-core' @@ -13,7 +15,7 @@ export function usePrefetchInfiniteQuery< TQueryKey extends QueryKey = QueryKey, TPageParam = unknown, >( - options: FetchInfiniteQueryOptions< + options: InfiniteQueryExecuteOptions< TQueryFnData, TError, TData, @@ -25,6 +27,6 @@ export function usePrefetchInfiniteQuery< const client = useQueryClient(queryClient) if (!client.getQueryState(options.queryKey)) { - client.prefetchInfiniteQuery(options) + void client.infiniteQuery(options).then(noop).catch(noop) } } diff --git a/packages/react-query/src/usePrefetchQuery.tsx b/packages/react-query/src/usePrefetchQuery.tsx index 3f508c33243..1b0fe52e108 100644 --- a/packages/react-query/src/usePrefetchQuery.tsx +++ b/packages/react-query/src/usePrefetchQuery.tsx @@ -1,6 +1,12 @@ +import { noop } from '@tanstack/query-core' import { useQueryClient } from './QueryClientProvider' -import type { DefaultError, QueryClient, QueryKey } from '@tanstack/query-core' -import type { UsePrefetchQueryOptions } from './types' + +import type { + DefaultError, + QueryClient, + QueryExecuteOptions, + QueryKey, +} from '@tanstack/query-core' export function usePrefetchQuery< TQueryFnData = unknown, @@ -8,12 +14,12 @@ export function usePrefetchQuery< TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, >( - options: UsePrefetchQueryOptions, + options: QueryExecuteOptions, queryClient?: QueryClient, ) { const client = useQueryClient(queryClient) if (!client.getQueryState(options.queryKey)) { - client.prefetchQuery(options) + void client.query(options).then(noop).catch(noop) } } diff --git a/packages/solid-query/src/__tests__/infiniteQueryOptions.test-d.tsx b/packages/solid-query/src/__tests__/infiniteQueryOptions.test-d.tsx index 5d212f7edf3..f08a38b9a1a 100644 --- a/packages/solid-query/src/__tests__/infiniteQueryOptions.test-d.tsx +++ b/packages/solid-query/src/__tests__/infiniteQueryOptions.test-d.tsx @@ -1,5 +1,5 @@ import { describe, expectTypeOf, it } from 'vitest' -import { dataTagSymbol } from '@tanstack/query-core' +import { QueryClient, dataTagSymbol } from '@tanstack/query-core' import { queryKey } from '@tanstack/query-test-utils' import { useInfiniteQuery } from '../useInfiniteQuery' import { infiniteQueryOptions } from '../infiniteQueryOptions' @@ -10,6 +10,33 @@ import type { } from '../infiniteQueryOptions' describe('infiniteQueryOptions', () => { + it('should work when passed to infiniteQuery', async () => { + const options = infiniteQueryOptions({ + getNextPageParam: () => 10, + queryKey: ['key'], + queryFn: () => ({ wow: true }), + initialPageParam: 0, + }) + + const data = await new QueryClient().infiniteQuery(options) + + expectTypeOf(data).toEqualTypeOf>() + }) + + it('should work when passed to infiniteQuery with select', async () => { + const options = infiniteQueryOptions({ + getNextPageParam: () => 10, + queryKey: ['key'], + queryFn: () => ({ wow: true }), + initialPageParam: 0, + select: (data) => data.pages, + }) + + const data = await new QueryClient().infiniteQuery(options) + + expectTypeOf(data).toEqualTypeOf>() + }) + it('should infer defined types', () => { const options = infiniteQueryOptions({ getNextPageParam: () => 10, diff --git a/packages/solid-query/src/__tests__/queryOptions.test-d.tsx b/packages/solid-query/src/__tests__/queryOptions.test-d.tsx index 299536290e6..e6b3d9b017d 100644 --- a/packages/solid-query/src/__tests__/queryOptions.test-d.tsx +++ b/packages/solid-query/src/__tests__/queryOptions.test-d.tsx @@ -39,6 +39,25 @@ describe('queryOptions', () => { const { data } = useQuery(() => options) expectTypeOf(data).toEqualTypeOf() }) + it('should work when passed to query', async () => { + const options = queryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + }) + + const data = await new QueryClient().query(options) + expectTypeOf(data).toEqualTypeOf() + }) + it('should work when passed to query with select', async () => { + const options = queryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + select: (data) => data.toString(), + }) + + const data = await new QueryClient().query(options) + expectTypeOf(data).toEqualTypeOf() + }) it('should work when passed to fetchQuery', async () => { const options = queryOptions({ queryKey: queryKey(), diff --git a/packages/svelte-query/tests/infiniteQueryOptions.test-d.ts b/packages/svelte-query/tests/infiniteQueryOptions.test-d.ts index 38bd9fc6706..ac2b3e98b63 100644 --- a/packages/svelte-query/tests/infiniteQueryOptions.test-d.ts +++ b/packages/svelte-query/tests/infiniteQueryOptions.test-d.ts @@ -48,6 +48,33 @@ describe('infiniteQueryOptions', () => { >() }) + test('Should work when passed to infiniteQuery', async () => { + const options = infiniteQueryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + getNextPageParam: () => 1, + initialPageParam: 1, + }) + + const data = await new QueryClient().infiniteQuery(options) + + expectTypeOf(data).toEqualTypeOf>() + }) + + test('Should work when passed to infiniteQuery with select', async () => { + const options = infiniteQueryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + getNextPageParam: () => 1, + initialPageParam: 1, + select: (data) => data.pages, + }) + + const data = await new QueryClient().infiniteQuery(options) + + expectTypeOf(data).toEqualTypeOf>() + }) + test('Should work when passed to fetchInfiniteQuery', async () => { const key = queryKey() const options = infiniteQueryOptions({ diff --git a/packages/svelte-query/tests/queryOptions.test-d.ts b/packages/svelte-query/tests/queryOptions.test-d.ts index 968d05409ad..379d89063ff 100644 --- a/packages/svelte-query/tests/queryOptions.test-d.ts +++ b/packages/svelte-query/tests/queryOptions.test-d.ts @@ -32,6 +32,27 @@ describe('queryOptions', () => { }) }) + test('Should work when passed to query', async () => { + const options = queryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + }) + + const data = await new QueryClient().query(options) + expectTypeOf(data).toEqualTypeOf() + }) + + test('Should work when passed to query with select', async () => { + const options = queryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + select: (data) => data.toString(), + }) + + const data = await new QueryClient().query(options) + expectTypeOf(data).toEqualTypeOf() + }) + test('Should work when passed to fetchQuery', async () => { const key = queryKey() const options = queryOptions({ diff --git a/packages/vue-query/src/__tests__/infiniteQueryOptions.test-d.ts b/packages/vue-query/src/__tests__/infiniteQueryOptions.test-d.ts index 2b15cb7bfed..5e09f5c0b44 100644 --- a/packages/vue-query/src/__tests__/infiniteQueryOptions.test-d.ts +++ b/packages/vue-query/src/__tests__/infiniteQueryOptions.test-d.ts @@ -1,6 +1,6 @@ import { assertType, describe, expectTypeOf, it } from 'vitest' import { dataTagSymbol } from '@tanstack/query-core' -import { reactive } from 'vue-demi' +import { reactive, unref } from 'vue-demi' import { queryKey } from '@tanstack/query-test-utils' import { infiniteQueryOptions } from '../infiniteQueryOptions' import { QueryClient } from '../queryClient' @@ -49,6 +49,41 @@ describe('infiniteQueryOptions', () => { InfiniteData | undefined >() }) + it('should work when passed to infiniteQuery', async () => { + const options = infiniteQueryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + getNextPageParam: () => 1, + initialPageParam: 1, + }) + + const data = await new QueryClient().infiniteQuery({ + ...unref(options), + enabled: true, + staleTime: 0, + pages: 1, + }) + + expectTypeOf(data).toEqualTypeOf>() + }) + it('should work when passed to infiniteQuery with select', async () => { + const options = infiniteQueryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + getNextPageParam: () => 1, + initialPageParam: 1, + select: (data) => data.pages, + }) + + const data = await new QueryClient().infiniteQuery({ + ...unref(options), + enabled: true, + staleTime: 0, + pages: 1, + }) + + expectTypeOf(data).toEqualTypeOf>() + }) it('should tag the queryKey with the result type of the QueryFn', () => { const key = queryKey() const { queryKey: tagged } = infiniteQueryOptions({ diff --git a/packages/vue-query/src/__tests__/queryClient.test-d.ts b/packages/vue-query/src/__tests__/queryClient.test-d.ts index 9af6d20307e..cab8f59c007 100644 --- a/packages/vue-query/src/__tests__/queryClient.test-d.ts +++ b/packages/vue-query/src/__tests__/queryClient.test-d.ts @@ -1,29 +1,30 @@ import { assertType, describe, expectTypeOf, it } from 'vitest' +import { skipToken } from '@tanstack/query-core' import { queryKey } from '@tanstack/query-test-utils' import { QueryClient } from '../queryClient' import type { DataTag, InfiniteData } from '@tanstack/query-core' describe('getQueryData', () => { it('should be typed if key is tagged', () => { - const queryKey = ['key'] as DataTag, number> + const key = ['key'] as DataTag, number> const queryClient = new QueryClient() - const data = queryClient.getQueryData(queryKey) + const data = queryClient.getQueryData(key) expectTypeOf(data).toEqualTypeOf() }) it('should infer unknown if key is not tagged', () => { - const queryKey = ['key'] as const + const key = ['key'] as const const queryClient = new QueryClient() - const data = queryClient.getQueryData(queryKey) + const data = queryClient.getQueryData(key) expectTypeOf(data).toEqualTypeOf() }) it('should infer passed generic if passed', () => { - const queryKey = ['key'] as const + const key = ['key'] as const const queryClient = new QueryClient() - const data = queryClient.getQueryData(queryKey) + const data = queryClient.getQueryData(key) expectTypeOf(data).toEqualTypeOf() }) @@ -38,9 +39,9 @@ describe('getQueryData', () => { describe('setQueryData', () => { it('updater should be typed if key is tagged', () => { - const queryKey = ['key'] as DataTag, number> + const key = ['key'] as DataTag, number> const queryClient = new QueryClient() - const data = queryClient.setQueryData(queryKey, (prev) => { + const data = queryClient.setQueryData(key, (prev) => { expectTypeOf(prev).toEqualTypeOf() return prev }) @@ -48,24 +49,24 @@ describe('setQueryData', () => { }) it('value should be typed if key is tagged', () => { - const queryKey = ['key'] as DataTag, number> + const key = ['key'] as DataTag, number> const queryClient = new QueryClient() // @ts-expect-error value should be a number - queryClient.setQueryData(queryKey, '1') + queryClient.setQueryData(key, '1') // @ts-expect-error value should be a number - queryClient.setQueryData(queryKey, () => '1') + queryClient.setQueryData(key, () => '1') - const data = queryClient.setQueryData(queryKey, 1) + const data = queryClient.setQueryData(key, 1) expectTypeOf(data).toEqualTypeOf() }) it('should infer unknown for updater if key is not tagged', () => { - const queryKey = ['key'] as const + const key = ['key'] as const const queryClient = new QueryClient() - const data = queryClient.setQueryData(queryKey, (prev) => { + const data = queryClient.setQueryData(key, (prev) => { expectTypeOf(prev).toEqualTypeOf() return prev }) @@ -73,17 +74,17 @@ describe('setQueryData', () => { }) it('should infer unknown for value if key is not tagged', () => { - const queryKey = ['key'] as const + const key = ['key'] as const const queryClient = new QueryClient() - const data = queryClient.setQueryData(queryKey, 'foo') + const data = queryClient.setQueryData(key, 'foo') expectTypeOf(data).toEqualTypeOf() }) it('should infer passed generic if passed', () => { - const queryKey = ['key'] as const + const key = ['key'] as const const queryClient = new QueryClient() - const data = queryClient.setQueryData(queryKey, (prev) => { + const data = queryClient.setQueryData(key, (prev) => { expectTypeOf(prev).toEqualTypeOf() return prev }) @@ -91,21 +92,21 @@ describe('setQueryData', () => { }) it('should infer passed generic for value', () => { - const queryKey = ['key'] as const + const key = ['key'] as const const queryClient = new QueryClient() - const data = queryClient.setQueryData(queryKey, 'foo') + const data = queryClient.setQueryData(key, 'foo') expectTypeOf(data).toEqualTypeOf() }) it('should preserve updater parameter type inference when used in functions with explicit return types', () => { - const queryKey = ['key'] as DataTag, number> + const key = ['key'] as DataTag, number> const queryClient = new QueryClient() // Simulate usage inside a function with explicit return type // The outer function returns 'unknown' but this shouldn't affect the updater's type inference ;(() => - queryClient.setQueryData(queryKey, (data) => { + queryClient.setQueryData(key, (data) => { expectTypeOf(data).toEqualTypeOf() return data })) satisfies () => unknown @@ -151,3 +152,108 @@ describe('fetchInfiniteQuery', () => { ]) }) }) + +describe('query', () => { + it('should return the selected type', () => { + const result = new QueryClient().query({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + select: (data) => data.length, + }) + + expectTypeOf(result).toEqualTypeOf>() + }) + + it('should infer select type with skipToken', () => { + const result = new QueryClient().query({ + queryKey: ['key'], + queryFn: skipToken, + select: (data: string) => data.length, + }) + + expectTypeOf(result).toEqualTypeOf>() + }) + + it('should infer select type with skipToken and enabled false', () => { + const result = new QueryClient().query({ + queryKey: ['key'], + queryFn: skipToken, + enabled: false, + select: (data: string) => data.length, + }) + + expectTypeOf(result).toEqualTypeOf>() + }) + + it('should infer select type with skipToken and enabled true', () => { + const result = new QueryClient().query({ + queryKey: ['key'], + queryFn: skipToken, + enabled: true, + select: (data: string) => data.length, + }) + + expectTypeOf(result).toEqualTypeOf>() + }) +}) + +describe('infiniteQuery', () => { + it('should return infinite data', async () => { + const data = await new QueryClient().infiniteQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + getNextPageParam: () => 1, + initialPageParam: 1, + }) + + expectTypeOf(data).toEqualTypeOf>() + }) + + it('should return the selected type', () => { + const result = new QueryClient().infiniteQuery({ + queryKey: ['key'], + queryFn: () => Promise.resolve({ count: 1 }), + getNextPageParam: () => 2, + initialPageParam: 1, + select: (data) => data.pages.map((page) => page.count), + }) + + expectTypeOf(result).toEqualTypeOf>>() + }) + + it('should allow passing pages with getNextPageParam', () => { + assertType>([ + { + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + initialPageParam: 1, + getNextPageParam: () => 1, + pages: 5, + }, + ]) + }) + + it('should not allow passing pages without getNextPageParam', () => { + assertType>([ + // @ts-expect-error Property 'getNextPageParam' is missing + { + queryKey: ['key'], + queryFn: () => Promise.resolve('string'), + initialPageParam: 1, + pages: 5, + }, + ]) + }) + + it('should preserve page param inference', () => { + new QueryClient().infiniteQuery({ + queryKey: ['key'], + queryFn: ({ pageParam }) => { + expectTypeOf(pageParam).toEqualTypeOf() + return Promise.resolve(pageParam.toString()) + }, + initialPageParam: 1, + getNextPageParam: () => undefined, + }) + }) +}) diff --git a/packages/vue-query/src/__tests__/queryClient.test.ts b/packages/vue-query/src/__tests__/queryClient.test.ts index 1a458e138f3..e722d9f6b6c 100644 --- a/packages/vue-query/src/__tests__/queryClient.test.ts +++ b/packages/vue-query/src/__tests__/queryClient.test.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest' -import { ref } from 'vue-demi' +import { ref, unref } from 'vue-demi' import { QueryClient as QueryClientOrigin } from '@tanstack/query-core' import { QueryClient } from '../queryClient' import { infiniteQueryOptions } from '../infiniteQueryOptions' @@ -338,6 +338,41 @@ describe('QueryCache', () => { }) }) + describe('query', () => { + test('should properly unwrap queryKey', () => { + const queryClient = new QueryClient() + + queryClient.query({ + queryKey: queryKeyRef, + }) + + expect(QueryClientOrigin.prototype.query).toBeCalledWith({ + queryKey: queryKeyUnref, + }) + }) + + test('should properly unwrap enabled, staleTime, and select', () => { + const queryClient = new QueryClient() + const enabled = () => false + const staleTime = () => 1000 + const select = (data: string) => data.length + + queryClient.query({ + queryKey: queryKeyRef, + enabled: ref(enabled), + staleTime: ref(staleTime), + select: ref(select), + }) + + expect(QueryClientOrigin.prototype.query).toBeCalledWith({ + queryKey: queryKeyUnref, + enabled, + staleTime, + select, + }) + }) + }) + describe('prefetchQuery', () => { test('should properly unwrap parameters', () => { const queryClient = new QueryClient() @@ -387,6 +422,59 @@ describe('QueryCache', () => { }) }) + describe('infiniteQuery', () => { + test('should properly unwrap queryKey, initialPageParam, pages, and select', () => { + const queryClient = new QueryClient() + const getNextPageParam = () => 1 + const select = (data: { pages: Array }) => data.pages.length + + queryClient.infiniteQuery({ + queryKey: queryKeyRef, + initialPageParam: ref(0), + pages: ref(2), + getNextPageParam: ref(getNextPageParam), + select: ref(select), + }) + + expect(QueryClientOrigin.prototype.infiniteQuery).toBeCalledWith( + expect.objectContaining({ + queryKey: queryKeyUnref, + initialPageParam: 0, + pages: 2, + getNextPageParam, + select, + }), + ) + }) + + test('should properly unwrap getNextPageParam when using infiniteQueryOptions', () => { + const queryClient = new QueryClient() + const getNextPageParam = () => 12 + + const options = infiniteQueryOptions({ + queryKey: queryKeyRef, + initialPageParam: ref(0), + getNextPageParam: ref(getNextPageParam), + }) + + queryClient.infiniteQuery({ + ...unref(options), + enabled: true, + staleTime: 0, + pages: 1, + }) + + expect(QueryClientOrigin.prototype.infiniteQuery).toBeCalledWith( + expect.objectContaining({ + queryKey: queryKeyUnref, + initialPageParam: 0, + pages: 1, + getNextPageParam, + }), + ) + }) + }) + describe('prefetchInfiniteQuery', () => { test('should properly unwrap parameters', () => { const queryClient = new QueryClient() diff --git a/packages/vue-query/src/__tests__/queryOptions.test-d.ts b/packages/vue-query/src/__tests__/queryOptions.test-d.ts index 3c98c30296b..ee0921bfe39 100644 --- a/packages/vue-query/src/__tests__/queryOptions.test-d.ts +++ b/packages/vue-query/src/__tests__/queryOptions.test-d.ts @@ -1,5 +1,5 @@ import { assertType, describe, expectTypeOf, it } from 'vitest' -import { reactive, ref } from 'vue-demi' +import { reactive, ref, unref } from 'vue-demi' import { dataTagSymbol } from '@tanstack/query-core' import { queryKey } from '@tanstack/query-test-utils' import { QueryClient } from '../queryClient' @@ -39,6 +39,33 @@ describe('queryOptions', () => { const { data } = reactive(useQuery(options)) expectTypeOf(data).toEqualTypeOf() }) + it('should work when passed to query', async () => { + const options = queryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + }) + + const data = await new QueryClient().query({ + ...unref(options), + enabled: true, + staleTime: 0, + }) + expectTypeOf(data).toEqualTypeOf() + }) + it('should work when passed to query with select', async () => { + const options = queryOptions({ + queryKey: ['key'], + queryFn: () => Promise.resolve(5), + select: (data) => data.toString(), + }) + + const data = await new QueryClient().query({ + ...unref(options), + enabled: true, + staleTime: 0, + }) + expectTypeOf(data).toEqualTypeOf() + }) it('should tag the queryKey with the result type of the QueryFn', () => { const key = queryKey() const { queryKey: tagged } = queryOptions({ @@ -130,10 +157,17 @@ describe('queryOptions', () => { // Should not error const data = queryClient.invalidateQueries(options) // Should not error - const data2 = queryClient.fetchQuery(options) + const data2 = queryClient.query({ + ...unref(options), + enabled: true, + staleTime: 0, + }) + // Should not error + const data3 = queryClient.fetchQuery(options) expectTypeOf(data).toEqualTypeOf>() expectTypeOf(data2).toEqualTypeOf>() + expectTypeOf(data3).toEqualTypeOf>() }) it('TData should always be defined when initialData is provided as a function which ALWAYS returns the data', () => { diff --git a/packages/vue-query/src/queryClient.ts b/packages/vue-query/src/queryClient.ts index 7f9a0894bfe..a62beabe319 100644 --- a/packages/vue-query/src/queryClient.ts +++ b/packages/vue-query/src/queryClient.ts @@ -16,6 +16,7 @@ import type { InferDataFromTag, InferErrorFromTag, InfiniteData, + InfiniteQueryExecuteOptions, InvalidateOptions, InvalidateQueryFilters, MutationFilters, @@ -23,6 +24,7 @@ import type { MutationObserverOptions, NoInfer, OmitKeyof, + QueryExecuteOptions, QueryFilters, QueryKey, QueryObserverOptions, @@ -66,6 +68,9 @@ export class QueryClient extends QC { return super.getQueryData(cloneDeepUnref(queryKey)) } + /** + * @deprecated Use queryClient.query({ ...options, staleTime: 'static' }) instead. This method will be removed in the next major version. + */ ensureQueryData< TQueryFnData, TError = DefaultError, @@ -74,6 +79,9 @@ export class QueryClient extends QC { >( options: EnsureQueryDataOptions, ): Promise + /** + * @deprecated Use queryClient.query({ ...options, staleTime: 'static' }) instead. This method will be removed in the next major version. + */ ensureQueryData< TQueryFnData, TError = DefaultError, @@ -253,6 +261,131 @@ export class QueryClient extends QC { ) } + query< + TQueryFnData, + TError = DefaultError, + TData = TQueryFnData, + TQueryData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = never, + >( + options: QueryExecuteOptions< + TQueryFnData, + TError, + TData, + TQueryData, + TQueryKey, + TPageParam + >, + ): Promise + query< + TQueryFnData, + TError = DefaultError, + TData = TQueryFnData, + TQueryData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = never, + >( + options: MaybeRefDeep< + QueryExecuteOptions< + TQueryFnData, + TError, + TData, + TQueryData, + TQueryKey, + TPageParam + > + >, + ): Promise + query< + TQueryFnData, + TError = DefaultError, + TData = TQueryFnData, + TQueryData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = never, + >( + options: MaybeRefDeep< + QueryExecuteOptions< + TQueryFnData, + TError, + TData, + TQueryData, + TQueryKey, + TPageParam + > + >, + ): Promise { + return super.query(cloneDeepUnref(options)) + } + + infiniteQuery< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, + >( + options: InfiniteQueryExecuteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + >, + ): Promise< + Array extends Array> + ? InfiniteData + : TData + > + infiniteQuery< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, + >( + options: MaybeRefDeep< + InfiniteQueryExecuteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + > + >, + ): Promise< + Array extends Array> + ? InfiniteData + : TData + > + infiniteQuery< + TQueryFnData, + TError = DefaultError, + TData = InfiniteData, + TQueryKey extends QueryKey = QueryKey, + TPageParam = unknown, + >( + options: MaybeRefDeep< + InfiniteQueryExecuteOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + > + >, + ): Promise< + Array extends Array> + ? InfiniteData + : TData + > { + return super.infiniteQuery(cloneDeepUnref(options)) + } + + /** + * @deprecated Use queryClient.query(options) instead. This method will be removed in the next major version. + */ fetchQuery< TQueryFnData, TError = DefaultError, @@ -268,6 +401,9 @@ export class QueryClient extends QC { TPageParam >, ): Promise + /** + * @deprecated Use queryClient.query(options) instead. This method will be removed in the next major version. + */ fetchQuery< TQueryFnData, TError = DefaultError, @@ -293,6 +429,9 @@ export class QueryClient extends QC { return super.fetchQuery(cloneDeepUnref(options)) } + /** + * @deprecated Use queryClient.query(options) instead. You can swallow errors with `.catch(noop)`. This method will be removed in the next major version. + */ prefetchQuery< TQueryFnData = unknown, TError = DefaultError, @@ -301,6 +440,9 @@ export class QueryClient extends QC { >( options: FetchQueryOptions, ): Promise + /** + * @deprecated Use queryClient.query(options) instead. You can swallow errors with `.catch(noop)`. This method will be removed in the next major version. + */ prefetchQuery< TQueryFnData = unknown, TError = DefaultError, @@ -324,6 +466,9 @@ export class QueryClient extends QC { return super.prefetchQuery(cloneDeepUnref(options)) } + /** + * @deprecated Use queryClient.infiniteQuery(options) instead. This method will be removed in the next major version. + */ fetchInfiniteQuery< TQueryFnData = unknown, TError = DefaultError, @@ -339,6 +484,9 @@ export class QueryClient extends QC { TPageParam >, ): Promise> + /** + * @deprecated Use queryClient.infiniteQuery(options) instead. This method will be removed in the next major version. + */ fetchInfiniteQuery< TQueryFnData, TError = DefaultError, @@ -376,6 +524,9 @@ export class QueryClient extends QC { return super.fetchInfiniteQuery(cloneDeepUnref(options)) } + /** + * @deprecated use void queryClient.infiniteQuery(options)instead. You can swallow errors with `.catch(noop)`. This method will be removed in the next major version. + */ prefetchInfiniteQuery< TQueryFnData, TError = DefaultError, @@ -391,6 +542,9 @@ export class QueryClient extends QC { TPageParam >, ): Promise + /** + * @deprecated use void queryClient.infiniteQuery(options)instead. You can swallow errors with `.catch(noop)`. This method will be removed in the next major version. + */ prefetchInfiniteQuery< TQueryFnData, TError = DefaultError,