diff --git a/.changeset/famous-owls-battle.md b/.changeset/famous-owls-battle.md new file mode 100644 index 00000000000..4a618fbd8a2 --- /dev/null +++ b/.changeset/famous-owls-battle.md @@ -0,0 +1,6 @@ +--- +'@tanstack/query-core': minor +'@tanstack/vue-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..50c1a608c86 100644 --- a/docs/framework/angular/guides/query-options.md +++ b/docs/framework/angular/guides/query-options.md @@ -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..54f7775075b 100644 --- a/docs/framework/angular/typescript.md +++ b/docs/framework/angular/typescript.md @@ -174,7 +174,7 @@ 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 imperative calls like `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 @Injectable({ @@ -215,11 +215,13 @@ export class Component { postQuery = injectQuery(this.optionsSignal) someMethod() { - this.queryClient.prefetchQuery(this.queries.post(23)) + this.queryClient.query(this.queries.post(23)).catch(noop) } } ``` +Because `queryClient.query` preserves `select` and `enabled`, the extracted options behave the same way in both places. The legacy `fetchQuery` and `prefetchQuery` APIs still accept those options at the type level, but they ignore `select` and `enabled` at runtime. + 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: ```ts 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..5332a41736a 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` @@ -494,7 +517,9 @@ Note that the infinite list must be bi-directional, which requires both `getNext ### Infinite Queries can prefetch multiple pages -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. Read the [prefetching guide](./prefetching.md) for more information. +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. + +If you are updating older v5 examples, prefer `queryClient.infiniteQuery(...)` here instead of `queryClient.prefetchInfiniteQuery(...)`. Like the other legacy imperative methods, `prefetchInfiniteQuery` is deprecated as of `INSERT_FUTURE_V5_MINOR`. Read the [prefetching guide](./prefetching.md) for the current pattern. ### New `combine` option for `useQueries` diff --git a/docs/framework/react/guides/prefetching.md b/docs/framework/react/guides/prefetching.md index c5dde014681..5d4038e6d77 100644 --- a/docs/framework/react/guides/prefetching.md +++ b/docs/framework/react/guides/prefetching.md @@ -16,30 +16,18 @@ 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 - -Before jumping into the different specific prefetch patterns, let's look at the `prefetchQuery` and `prefetchInfiniteQuery` functions. First a few basics: - -- 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. - - 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. - -This is how you use `prefetchQuery`: - [//]: # 'ExamplePrefetchQuery' ```tsx -const prefetchTodos = async () => { +const prefetchTodos = () => { // The results of this query will be cached like a normal query - await queryClient.prefetchQuery({ - queryKey: ['todos'], - queryFn: fetchTodos, - }) + void queryClient + .query({ + queryKey: ['todos'], + queryFn: fetchTodos, + // Swallow errors here, because usually they will fetch again in `useQuery` + }) + .catch(noop) } ``` @@ -50,9 +38,9 @@ Infinite Queries can be prefetched like regular Queries. Per default, only the f [//]: # 'ExamplePrefetchInfiniteQuery' ```tsx -const prefetchProjects = async () => { +const prefetchProjects = () => { // The results of this query will be cached like a normal query - await queryClient.prefetchInfiniteQuery({ + void queryClient.infiniteQuery({ queryKey: ['projects'], queryFn: fetchProjects, initialPageParam: 0, @@ -68,7 +56,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 +65,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,17 +212,19 @@ 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 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.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) }, @@ -247,10 +237,12 @@ Prefetching in an effect also works, but note that if you are using `useSuspense 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]) ``` @@ -334,10 +326,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) } } @@ -394,10 +386,10 @@ const articleRoute = new Route({ routeContext: { articleQueryOptions, commentsQueryOptions }, }) => { // Fetch comments asap, but don't block - queryClient.prefetchQuery(commentsQueryOptions) + void queryClient.query(commentsQueryOptions).catch(noop) // Don't render the route at all until article has been fetched - await queryClient.prefetchQuery(articleQueryOptions) + await queryClient.query(articleQueryOptions).catch(noop) }, 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..3af893fa35c 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(...).catch(noop)` for each query you want to prefetch for critical rendered content - 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..ee59801a49c 100644 --- a/docs/framework/react/reference/infiniteQueryOptions.md +++ b/docs/framework/react/reference/infiniteQueryOptions.md @@ -12,7 +12,9 @@ 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`. + +Options like `select` and `enabled` keep working when you pass the result to `queryClient.infiniteQuery`, because `infiniteQuery()` honors the same imperative query semantics. Legacy methods like `fetchInfiniteQuery` and `prefetchInfiniteQuery` ignore `select` and `enabled`, even though TypeScript still accepts those excess properties. - `queryKey: QueryKey` - **Required** diff --git a/docs/framework/react/reference/queryOptions.md b/docs/framework/react/reference/queryOptions.md index b6c5409372d..f0a15dadec2 100644 --- a/docs/framework/react/reference/queryOptions.md +++ b/docs/framework/react/reference/queryOptions.md @@ -12,7 +12,9 @@ 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`. + +Options like `select` and `enabled` keep working when you pass the result to `queryClient.query`, because `query()` honors the same imperative query semantics. Legacy methods like `fetchQuery` and `prefetchQuery` ignore `select` and `enabled`, even though TypeScript still accepts those excess properties. - `queryKey: QueryKey` - **Required** diff --git a/docs/framework/react/reference/usePrefetchInfiniteQuery.md b/docs/framework/react/reference/usePrefetchInfiniteQuery.md index 1e86a35b55d..c5775e7ec1c 100644 --- a/docs/framework/react/reference/usePrefetchInfiniteQuery.md +++ b/docs/framework/react/reference/usePrefetchInfiniteQuery.md @@ -11,6 +11,8 @@ usePrefetchInfiniteQuery(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: +For general imperative fetching outside render, prefer [`queryClient.infiniteQuery`](../../../reference/QueryClient.md#queryclientinfinitequery). This hook specifically mirrors the legacy prefetch behavior during render. + - `queryKey: QueryKey` - **Required** - The query key to prefetch during render diff --git a/docs/framework/react/reference/usePrefetchQuery.md b/docs/framework/react/reference/usePrefetchQuery.md index 7feccad5fc7..2545e6c5e69 100644 --- a/docs/framework/react/reference/usePrefetchQuery.md +++ b/docs/framework/react/reference/usePrefetchQuery.md @@ -11,6 +11,8 @@ usePrefetchQuery(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: +For general imperative fetching outside render, prefer [`queryClient.query`](../../../reference/QueryClient.md#queryclientquery). This hook specifically mirrors the legacy prefetch behavior during render. + - `queryKey: QueryKey` - **Required** - The query key to prefetch during render 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..641b3c15814 100644 --- a/docs/framework/solid/guides/prefetching.md +++ b/docs/framework/solid/guides/prefetching.md @@ -86,17 +86,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 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.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 +113,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 +189,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) } } @@ -234,10 +238,10 @@ const articleRoute = new Route({ routeContext: { articleQueryOptions, commentsQueryOptions }, }) => { // Fetch comments asap, but don't block - queryClient.prefetchQuery(commentsQueryOptions) + void queryClient.query(commentsQueryOptions).catch(noop) // Don't render the route at all until article has been fetched - await queryClient.prefetchQuery(articleQueryOptions) + await queryClient.query(articleQueryOptions).catch(noop) }, 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. - -## Using Vite SSR - -Sync VueQuery client state with [vite-ssr](https://github.com/frandiox/vite-ssr) in order to serialize it in the DOM: - -```js -// main.js (entry point) -import App from './App.vue' -import viteSSR from 'vite-ssr/vue' -import { - QueryClient, - VueQueryPlugin, - hydrate, - dehydrate, -} from '@tanstack/vue-query' - -export default viteSSR(App, { routes: [] }, ({ app, initialState }) => { - // -- This is Vite SSR main hook, which is called once per request - - // Create a fresh VueQuery client - const queryClient = new QueryClient() - - // Sync initialState with the client state - if (import.meta.env.SSR) { - // Indicate how to access and serialize VueQuery state during SSR - initialState.vueQueryState = { toJSON: () => dehydrate(queryClient) } - } else { - // Reuse the existing state in the browser - hydrate(queryClient, initialState.vueQueryState) - } - - // Mount and provide the client to the app components - app.use(VueQueryPlugin, { queryClient }) -}) -``` +i``` 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 `queryma` or `suspense` for a specific query. ## Using +Vite SSR Sync VueQuery client state with +[vite-ssr](https://github.com/frandiox/vite-ssr) in order to serialize it in the +DOM: ```js // main.js (entry point) import App from './App.vue' import viteSSR +from 'vite-ssr/vue' import { QueryClient, VueQueryPlugin, hydrate, dehydrate, } +from '@tanstack/vue-query' export default viteSSR(App, { routes: [] }, ({ app, +initialState }) => { // -- This is Vite SSR main hook, which is called once per +request // Create a fresh VueQuery client const queryClient = new QueryClient() +// Sync initialState with the client state if (import.meta.env.SSR) { // +Indicate how to access and serialize VueQuery state during SSR +initialState.vueQueryState = { toJSON: () => dehydrate(queryClient) } } else { +// Reuse the existing state in the browser hydrate(queryClient, +initialState.vueQueryState) } // Mount and provide the client to the app +components app.use(VueQueryPlugin, { queryClient }) }) +```` Then, call VueQuery from any component using Vue's `onServerPrefetch`: @@ -218,9 +198,11 @@ Then, call VueQuery from any component using Vue's `onServerPrefetch`: