snapshot of https://github.com/TanStack/query.git (main, 2026-09-16; blobs > 131072B omitted)
Some checks failed
autofix.ci / autofix (push) Failing after 4s
Release / Release (push) Has been skipped
GitHub Actions Security Analysis / Run zizmor (push) Failing after 2s

This commit is contained in:
upstream-mirror
2026-09-16 01:13:08 +05:00
commit 3a013ccdfe
3570 changed files with 490194 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
---
id: background-fetching-indicators
title: Background Fetching Indicators
ref: docs/framework/react/guides/background-fetching-indicators.md
---
[//]: # 'Example'
```vue
<script setup>
import { useQuery } from '@tanstack/vue-query'
const { isPending, isFetching, isError, data, error } = useQuery({
queryKey: ['todos'],
queryFn: getTodos,
})
</script>
<template>
<div v-if="isFetching">Refreshing...</div>
<span v-if="isPending">Loading...</span>
<span v-else-if="isError">Error: {{ error.message }}</span>
<!-- We can assume by this point that `isSuccess === true` -->
<ul v-else-if="data">
<li v-for="todo in data" :key="todo.id">{{ todo.title }}</li>
</ul>
</template>
```
[//]: # 'Example'
[//]: # 'Example2'
```vue
<script setup>
import { useIsFetching } from '@tanstack/vue-query'
const isFetching = useIsFetching()
</script>
<template>
<div v-if="isFetching">Queries are fetching in the background...</div>
</template>
```
[//]: # 'Example2'

View File

@@ -0,0 +1,5 @@
---
id: caching
title: Caching Examples
ref: docs/framework/react/guides/caching.md
---

View File

@@ -0,0 +1,72 @@
---
id: custom-client
title: Custom Client
---
### Custom client
Vue Query allows providing custom `QueryClient` for Vue context.
It might be handy when you need to create `QueryClient` beforehand to integrate it with other libraries that do not have access to the Vue context.
For this reason, `VueQueryPlugin` accepts either `QueryClientConfig` or `QueryClient` as a plugin options.
If You provide `QueryClientConfig`, `QueryClient` instance will be created internally and provided to Vue context.
```tsx
const vueQueryPluginOptions: VueQueryPluginOptions = {
queryClientConfig: {
defaultOptions: { queries: { staleTime: 3600 } },
},
}
app.use(VueQueryPlugin, vueQueryPluginOptions)
```
```tsx
const myClient = new QueryClient(queryClientConfig)
const vueQueryPluginOptions: VueQueryPluginOptions = {
queryClient: myClient,
}
app.use(VueQueryPlugin, vueQueryPluginOptions)
```
### Custom context key
You can also customize the key under which `QueryClient` will be accessible in Vue context. This can be useful is you want to avoid name clashing between multiple apps on the same page with Vue2.
It works both with default, and custom `QueryClient`
```tsx
const vueQueryPluginOptions: VueQueryPluginOptions = {
queryClientKey: 'Foo',
}
app.use(VueQueryPlugin, vueQueryPluginOptions)
```
```tsx
const myClient = new QueryClient(queryClientConfig)
const vueQueryPluginOptions: VueQueryPluginOptions = {
queryClient: myClient,
queryClientKey: 'Foo',
}
app.use(VueQueryPlugin, vueQueryPluginOptions)
```
To use the custom client key, You have to provide it as a query options
```js
useQuery({
queryKey: ['query1'],
queryFn: fetcher,
queryClientKey: 'foo',
})
```
Internally custom key will be combined with default query key as a suffix. But user do not have to worry about it.
```tsx
const vueQueryPluginOptions: VueQueryPluginOptions = {
queryClientKey: 'Foo',
}
app.use(VueQueryPlugin, vueQueryPluginOptions) // -> VUE_QUERY_CLIENT:Foo
```

View File

@@ -0,0 +1,32 @@
---
id: default-query-function
title: Default Query Function
ref: docs/framework/react/guides/default-query-function.md
---
[//]: # 'Example'
```tsx
// Define a default query function that will receive the query key
const defaultQueryFn = async ({ queryKey }) => {
const { data } = await axios.get(
`https://jsonplaceholder.typicode.com${queryKey[0]}`,
)
return data
}
// provide the default query function to your app with defaultOptions
const vueQueryPluginOptions: VueQueryPluginOptions = {
queryClientConfig: {
defaultOptions: { queries: { queryFn: defaultQueryFn } },
},
}
app.use(VueQueryPlugin, vueQueryPluginOptions)
// All you have to do now is pass a key!
const { status, data, error, isFetching } = useQuery({
queryKey: [`/posts/${postId}`],
})
```
[//]: # 'Example'

View File

@@ -0,0 +1,55 @@
---
id: dependent-queries
title: Dependent Queries
ref: docs/framework/react/guides/dependent-queries.md
---
[//]: # 'Example'
```js
// Get the user
const { data: user } = useQuery({
queryKey: ['user', email],
queryFn: () => getUserByEmail(email.value),
})
const userId = computed(() => user.value?.id)
const enabled = computed(() => !!user.value?.id)
// Then get the user's projects
const { isIdle, data: projects } = useQuery({
queryKey: ['projects', userId],
queryFn: () => getProjectsByUser(userId.value),
enabled, // The query will not execute until `enabled == true`
})
```
[//]: # 'Example'
[//]: # 'Example2'
```tsx
// Get the users ids
const { data: userIds } = useQuery({
queryKey: ['users'],
queryFn: getUsersData,
select: (users) => users.map((user) => user.id),
})
const queries = computed(() => {
return userIds.value.length
? userIds.value.map((id) => {
return {
queryKey: ['messages', id],
queryFn: () => getMessagesByUsers(id),
}
})
: []
})
// Then get the users messages
const usersMessages = useQueries({
queries, // if userIds.value is undefined or has no items, an empty array will be returned
})
```
[//]: # 'Example2'

View File

@@ -0,0 +1,79 @@
---
id: disabling-queries
title: Disabling/Pausing Queries
ref: docs/framework/react/guides/disabling-queries.md
---
[//]: # 'Example'
```vue
<script setup>
import { useQuery } from '@tanstack/vue-query'
const { isLoading, isError, data, error, refetch, isFetching } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodoList,
enabled: false,
})
</script>
<template>
<button @click="refetch()">Fetch Todos</button>
<span v-if="isLoading">Loading...</span>
<span v-else-if="isError">Error: {{ error?.message }}</span>
<div v-else-if="data">
<span v-if="isFetching">Fetching...</span>
<ul>
<li v-for="todo in data" :key="todo.id">{{ todo.title }}</li>
</ul>
</div>
<span v-else>Not ready...</span>
</template>
```
[//]: # 'Example'
[//]: # 'Example2'
```vue
<script setup>
import { useQuery } from '@tanstack/vue-query'
const filter = ref('')
const isEnabled = computed(() => !!filter.value)
const { data } = useQuery({
queryKey: ['todos', filter],
queryFn: () => fetchTodos(filter),
// ⬇️ disabled as long as the filter is empty
enabled: isEnabled,
})
</script>
<template>
<span v-if="data">Filter was set and data is here!</span>
</template>
```
[//]: # 'Example2'
[//]: # 'Example3'
```vue
<script setup>
import { useQuery, skipToken } from '@tanstack/vue-query'
const filter = ref('')
const queryFn = computed(() =>
!!filter.value ? () => fetchTodos(filter) : skipToken,
)
const { data } = useQuery({
queryKey: ['todos', filter],
// ⬇️ disabled as long as the filter is undefined or empty
queryFn: queryFn,
})
</script>
<template>
<span v-if="data">Filter was set and data is here!</span>
</template>
```
[//]: # 'Example3'

View File

@@ -0,0 +1,6 @@
---
id: does-this-replace-client-state
title: Does Vue Query replace Vuex, Pinia or other global state managers?
ref: docs/framework/react/guides/does-this-replace-client-state.md
replace: { 'Redux, MobX': 'Vuex, Pinia' }
---

View File

@@ -0,0 +1,5 @@
---
id: filters
title: Filters
ref: docs/framework/react/guides/filters.md
---

View File

@@ -0,0 +1,8 @@
---
id: important-defaults
title: Important Defaults
ref: docs/framework/react/guides/important-defaults.md
---
[//]: # 'Materials'
[//]: # 'Materials'

View File

@@ -0,0 +1,57 @@
---
id: infinite-queries
title: Infinite Queries
ref: docs/framework/react/guides/infinite-queries.md
---
[//]: # 'Example'
```vue
<script setup>
import { useInfiniteQuery } from '@tanstack/vue-query'
const fetchProjects = async ({ pageParam }) => {
const res = await fetch('/api/projects?cursor=' + pageParam)
return res.json()
}
const {
data,
error,
fetchNextPage,
hasNextPage,
isFetching,
isFetchingNextPage,
isPending,
isError,
} = useInfiniteQuery({
queryKey: ['projects'],
queryFn: fetchProjects,
initialPageParam: 0,
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
})
</script>
<template>
<span v-if="isPending">Loading...</span>
<span v-else-if="isError">Error: {{ error.message }}</span>
<div v-else-if="data">
<span v-if="isFetching && !isFetchingNextPage">Fetching...</span>
<ul v-for="(group, index) in data.pages" :key="index">
<li v-for="project in group.projects" :key="project.id">
{{ project.name }}
</li>
</ul>
<button
@click="() => fetchNextPage()"
:disabled="!hasNextPage || isFetchingNextPage"
>
<span v-if="isFetchingNextPage">Loading more...</span>
<span v-else-if="hasNextPage">Load More</span>
<span v-else>Nothing more to load</span>
</button>
</div>
</template>
```
[//]: # 'Example'

View File

@@ -0,0 +1,8 @@
---
id: initial-query-data
title: Initial Query Data
ref: docs/framework/react/guides/initial-query-data.md
---
[//]: # 'Materials'
[//]: # 'Materials'

View File

@@ -0,0 +1,24 @@
---
id: invalidations-from-mutations
title: Invalidations from Mutations
ref: docs/framework/react/guides/invalidations-from-mutations.md
---
[//]: # 'Example2'
```tsx
import { useMutation, useQueryClient } from '@tanstack/vue-query'
const queryClient = useQueryClient()
// When this mutation succeeds, invalidate any queries with the `todos` or `reminders` query key
const mutation = useMutation({
mutationFn: addTodo,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] })
queryClient.invalidateQueries({ queryKey: ['reminders'] })
},
})
```
[//]: # 'Example2'

View File

@@ -0,0 +1,41 @@
---
id: migrating-to-tanstack-query-5
title: Migrating to TanStack Query v5
ref: docs/framework/react/guides/migrating-to-v5.md
---
[//]: # 'FrameworkSpecificBreakingChanges'
## Vue Query Breaking Changes
### `useQueries` composable returns `ref` instead of `reactive`
To fix compatibility with Vue 2, `useQueries` composable now returns `queries` array wrapped in `ref`.
Previously `reactive` was returned which led to multiple problems:
- User could spread return value loosing reactivity.
- `readonly` wrapper used for return value was breaking Vue 2 reactivity detection mechanism. This was a silent issue in Vue 2.6, but appeared as error in Vue 2.7.
- Vue 2 does not support arrays as a root value of `reactive`.
With this change all of those issues are fixed.
Also this aligns `useQueries` with other composables which return all of the values as `refs`.
### Vue v3.3 is now required
To be able to provide new features following Vue releases, we now require Vue 3 to be at least in v3.3 version.
Requirements for Vue 2.x remain unchanged.
[//]: # 'FrameworkSpecificBreakingChanges'
[//]: # 'FrameworkSpecificNewFeatures'
### Ability to run `vue-query` composables in `injectionContext`
Previously `vue-query` composables could be run only within `setup` function of the component.
We had an escape hatch in place to allow those hooks to be run anywhere if user would provide `queryClient` as a composable option.
Now you can use `vue-query` composables in any function that supports `injectionContext`. Ex. router navigation guards.
When using this new feature, make sure that `vue-query` composable is running within `effectScope`. Otherwise it might lead to memory leaks.
We have added `dev-only` warnings to inform users about potential misusage.
[//]: # 'FrameworkSpecificNewFeatures'

View File

@@ -0,0 +1,97 @@
---
id: mutations
title: Mutations
ref: docs/framework/react/guides/mutations.md
---
[//]: # 'Example'
```vue
<script setup>
import { useMutation } from '@tanstack/vue-query'
const { isPending, isError, error, isSuccess, mutate } = useMutation({
mutationFn: (newTodo) => axios.post('/todos', newTodo),
})
function addTodo() {
mutate({ id: new Date(), title: 'Do Laundry' })
}
</script>
<template>
<span v-if="isPending">Adding todo...</span>
<span v-else-if="isError">An error occurred: {{ error.message }}</span>
<span v-else-if="isSuccess">Todo added!</span>
<button @click="addTodo">Create Todo</button>
</template>
```
[//]: # 'Example'
[//]: # 'Info1'
[//]: # 'Info1'
[//]: # 'Example2'
[//]: # 'Example2'
[//]: # 'Example3'
```vue
<script>
import { useMutation } from '@tanstack/vue-query'
const { error, mutate, reset } = useMutation({
mutationFn: (newTodo) => axios.post('/todos', newTodo),
})
function addTodo() {
mutate({ id: new Date(), title: 'Do Laundry' })
}
</script>
<template>
<span v-else-if="error">
<span>An error occurred: {{ error.message }}</span>
<button @click="reset">Reset error</button>
</span>
<button @click="addTodo">Create Todo</button>
</template>
```
[//]: # 'Example3'
[//]: # 'Example11'
```js
const client = new QueryClient({
defaultOptions: {
queries: {
gcTime: 1000 * 60 * 60 * 24, // 24 hours
},
},
})
// we need a default mutation function so that paused mutations can resume after a page reload
queryClient.setMutationDefaults({
mutationKey: ['todos'],
mutationFn: ({ id, data }) => {
return api.updateTodo(id, data)
},
})
const vueQueryOptions: VueQueryPluginOptions = {
queryClient: client,
clientPersister: (queryClient) => {
return persistQueryClient({
queryClient,
persister: createAsyncStoragePersister({ storage: localStorage }),
})
},
clientPersisterOnSuccess: (queryClient) => {
queryClient.resumePausedMutations()
},
}
createApp(App).use(VueQueryPlugin, vueQueryOptions).mount('#app')
```
[//]: # 'Example11'
[//]: # 'Materials'
[//]: # 'Materials'

View File

@@ -0,0 +1,5 @@
---
id: network-mode
title: Network Mode
ref: docs/framework/react/guides/network-mode.md
---

View File

@@ -0,0 +1,6 @@
---
id: optimistic-updates
title: Optimistic Updates
ref: docs/framework/react/guides/optimistic-updates.md
replace: { 'React': 'Vue' }
---

View File

@@ -0,0 +1,53 @@
---
id: paginated-queries
title: Paginated / Lagged Queries
ref: docs/framework/react/guides/paginated-queries.md
---
[//]: # 'Example2'
```vue
<script setup lang="ts">
import { ref, Ref } from 'vue'
import { useQuery, keepPreviousData } from '@tanstack/vue-query'
const fetcher = (page: Ref<number>) =>
fetch(
`https://jsonplaceholder.typicode.com/posts?_page=${page.value}&_limit=10`,
).then((response) => response.json())
const page = ref(1)
const { isPending, isError, data, error, isFetching, isPlaceholderData } =
useQuery({
queryKey: ['projects', page],
queryFn: () => fetcher(page),
placeholderData: keepPreviousData,
})
const prevPage = () => {
page.value = Math.max(page.value - 1, 1)
}
const nextPage = () => {
if (!isPlaceholderData.value) {
page.value = page.value + 1
}
}
</script>
<template>
<h1>Posts</h1>
<p>Current Page: {{ page }} | Previous data: {{ isPlaceholderData }}</p>
<button @click="prevPage">Prev Page</button>
<button @click="nextPage">Next Page</button>
<div v-if="isPending">Loading...</div>
<div v-else-if="isError">An error has occurred: {{ error }}</div>
<div v-else-if="data">
<ul>
<li v-for="item in data" :key="item.id">
{{ item.title }}
</li>
</ul>
</div>
</template>
```
[//]: # 'Example2'

View File

@@ -0,0 +1,38 @@
---
id: parallel-queries
title: Parallel Queries
ref: docs/framework/react/guides/parallel-queries.md
---
[//]: # 'Example'
```vue
<script setup lang="ts">
// The following queries will execute in parallel
const usersQuery = useQuery({ queryKey: ['users'], queryFn: fetchUsers })
const teamsQuery = useQuery({ queryKey: ['teams'], queryFn: fetchTeams })
const projectsQuery = useQuery({
queryKey: ['projects'],
queryFn: fetchProjects,
})
</script>
```
[//]: # 'Example'
[//]: # 'Info'
[//]: # 'Info'
[//]: # 'Example2'
```js
const users = computed(...)
const queries = computed(() => users.value.map(user => {
return {
queryKey: ['user', user.id],
queryFn: () => fetchUserById(user.id),
}
})
);
const userQueries = useQueries({queries: queries})
```
[//]: # 'Example2'

View File

@@ -0,0 +1,38 @@
---
id: placeholder-query-data
title: Placeholder Query Data
ref: docs/framework/react/guides/placeholder-query-data.md
---
[//]: # 'ExampleValue'
```tsx
const result = useQuery({
queryKey: ['todos'],
queryFn: () => fetch('/todos'),
placeholderData: placeholderTodos,
})
```
[//]: # 'ExampleValue'
[//]: # 'Memoization'
[//]: # 'Memoization'
[//]: # 'ExampleCache'
```tsx
const result = useQuery({
queryKey: ['blogPost', blogPostId],
queryFn: () => fetch(`/blogPosts/${blogPostId}`),
placeholderData: () => {
// Use the smaller/preview version of the blogPost from the 'blogPosts'
// query as the placeholder data for this blogPost query
return queryClient
.getQueryData(['blogPosts'])
?.find((d) => d.id === blogPostId)
},
})
```
[//]: # 'ExampleCache'
[//]: # 'Materials'
[//]: # 'Materials'

View File

@@ -0,0 +1,9 @@
---
id: polling
title: Polling
ref: docs/framework/react/guides/polling.md
replace: { '@tanstack/react-query': '@tanstack/vue-query' }
---
[//]: # 'ReactNative'
[//]: # 'ReactNative'

View File

@@ -0,0 +1,57 @@
---
id: prefetching
title: Prefetching
---
If you're lucky enough, you may know enough about what your users will do to be able to prefetch the data they need before it's needed. If this is the case, use `queryClient.query` or `queryClient.infiniteQuery` to warm the cache ahead of time:
[//]: # 'ExamplePrefetching'
```tsx
import { noop } from '@tanstack/vue-query'
const prefetchTodos = async () => {
// The results of this query will be cached like a normal query
await queryClient
.query({
queryKey: ['todos'],
queryFn: fetchTodos,
})
.catch(noop)
}
```
[//]: # 'ExamplePrefetching'
- If **fresh** data for this query is already in the cache, the data will not be fetched
- If a `staleTime` is passed e.g. `queryClient.query({ queryKey: ['todos'], queryFn: fn, staleTime: 5000 })` and the data is older than the specified `staleTime`, the query will be fetched
- As `useQuery` will retry fetches and handle errors, you can use `void` to ignore the promise from `query` and `.catch(noop)` to ignore errors.
- If you want to always return cached data when it exists, use `staleTime: 'static'`
- If no instances of `useQuery` appear for a prefetched query, it will be deleted and garbage collected after the time specified in `gcTime`.
## Prefetching Infinite Queries
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:
[//]: # 'ExampleInfiniteQuery'
```tsx
import { noop } from '@tanstack/vue-query'
const prefetchProjects = async () => {
// The results of this query will be cached like a normal query
await queryClient
.infiniteQuery({
queryKey: ['projects'],
queryFn: fetchProjects,
initialPageParam: 0,
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
pages: 3, // prefetch the first 3 pages
})
.catch(noop)
}
```
[//]: # 'ExampleInfiniteQuery'
The above code will try to prefetch 3 pages in order, and `getNextPageParam` will be executed for each page to determine the next page to prefetch. If `getNextPageParam` returns `undefined`, the prefetching will stop.

View File

@@ -0,0 +1,63 @@
---
id: queries
title: Queries
ref: docs/framework/react/guides/queries.md
---
[//]: # 'Example'
```ts
import { useQuery } from '@tanstack/vue-query'
const result = useQuery({ queryKey: ['todos'], queryFn: fetchTodoList })
```
[//]: # 'Example'
[//]: # 'Example3'
```vue
<script setup>
import { useQuery } from '@tanstack/vue-query'
const { isPending, isError, data, error } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodoList,
})
</script>
<template>
<span v-if="isPending">Loading...</span>
<span v-else-if="isError">Error: {{ error.message }}</span>
<!-- We can assume by this point that `isSuccess === true` -->
<ul v-else-if="data">
<li v-for="todo in data" :key="todo.id">{{ todo.title }}</li>
</ul>
</template>
```
[//]: # 'Example3'
[//]: # 'Example4'
```vue
<script setup>
import { useQuery } from '@tanstack/vue-query'
const { status, data, error } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodoList,
})
</script>
<template>
<span v-if="status === 'pending'">Loading...</span>
<span v-else-if="status === 'error'">Error: {{ error.message }}</span>
<!-- also status === 'success', but "else" logic works, too -->
<ul v-else-if="data">
<li v-for="todo in data" :key="todo.id">{{ todo.title }}</li>
</ul>
</template>
```
[//]: # 'Example4'
[//]: # 'Materials'
[//]: # 'Materials'

View File

@@ -0,0 +1,25 @@
---
id: query-cancellation
title: Query Cancellation
ref: docs/framework/react/guides/query-cancellation.md
---
[//]: # 'Example7'
```ts
const query = useQuery({
queryKey: ['todos'],
queryFn: async ({ signal }) => {
const resp = await fetch('/todos', { signal })
return resp.json()
},
})
const queryClient = useQueryClient()
function onButtonClick() {
queryClient.cancelQueries({ queryKey: ['todos'] })
}
```
[//]: # 'Example7'

View File

@@ -0,0 +1,22 @@
---
id: query-functions
title: Query Functions
ref: docs/framework/react/guides/query-functions.md
---
[//]: # 'Example4'
```js
const result = useQuery({
queryKey: ['todos', { status, page }],
queryFn: fetchTodoList,
})
// Access the key, status and page variables in your query function!
function fetchTodoList({ queryKey }) {
const [_key, { status, page }] = queryKey
return new Promise()
}
```
[//]: # 'Example4'

View File

@@ -0,0 +1,6 @@
---
id: query-invalidation
title: Query Invalidation
ref: docs/framework/react/guides/query-invalidation.md
replace: { 'react-query': 'vue-query' }
---

View File

@@ -0,0 +1,21 @@
---
id: query-keys
title: Query Keys
ref: docs/framework/react/guides/query-keys.md
---
[//]: # 'Example5'
```ts
import type { Ref } from 'vue'
function useTodos(todoId: Ref<string>) {
const queryKey = ['todos', todoId]
return useQuery({
queryKey,
queryFn: () => fetchTodoById(todoId.value),
})
}
```
[//]: # 'Example5'

View File

@@ -0,0 +1,6 @@
---
id: query-options
title: Query Options
ref: docs/framework/react/guides/query-options.md
replace: { '@tanstack/react-query': '@tanstack/vue-query' }
---

View File

@@ -0,0 +1,39 @@
---
id: query-retries
title: Query Retries
ref: docs/framework/react/guides/query-retries.md
replace: { 'Provider': 'Plugin' }
---
[//]: # 'Example'
```tsx
import { useQuery } from '@tanstack/vue-query'
// Make a specific query retry a certain number of times
const result = useQuery({
queryKey: ['todos', 1],
queryFn: fetchTodoListPage,
retry: 10, // Will retry failed requests 10 times before displaying an error
})
```
[//]: # 'Example'
[//]: # 'Example2'
```ts
import { VueQueryPlugin } from '@tanstack/vue-query'
const vueQueryPluginOptions = {
queryClientConfig: {
defaultOptions: {
queries: {
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
},
},
},
}
app.use(VueQueryPlugin, vueQueryPluginOptions)
```
[//]: # 'Example2'

View File

@@ -0,0 +1,5 @@
---
id: scroll-restoration
title: Scroll Restoration
ref: docs/framework/react/guides/scroll-restoration.md
---

View File

@@ -0,0 +1,278 @@
---
id: ssr
title: SSR
---
Vue Query supports prefetching multiple queries on the server and then _dehydrating_ those queries to the queryClient. This means the server can prerender markup that is immediately available on page load and as soon as JS is available, Vue Query can upgrade or _hydrate_ those queries with the full functionality of the library. This includes refetching those queries on the client if they have become stale since the time they were rendered on the server.
## Using Nuxt.js
### Nuxt 3
First create `vue-query.ts` file in your `plugins` directory with the following content:
```ts
import type {
DehydratedState,
VueQueryPluginOptions,
} from '@tanstack/vue-query'
import {
VueQueryPlugin,
QueryClient,
hydrate,
dehydrate,
} from '@tanstack/vue-query'
// Nuxt 3 app aliases
import { defineNuxtPlugin, useState } from '#imports'
export default defineNuxtPlugin((nuxt) => {
const vueQueryState = useState<DehydratedState | null>('vue-query')
// Modify your Vue Query global settings here
const queryClient = new QueryClient({
defaultOptions: { queries: { staleTime: 5000 } },
})
const options: VueQueryPluginOptions = { queryClient }
nuxt.vueApp.use(VueQueryPlugin, options)
if (import.meta.server) {
nuxt.hooks.hook('app:rendered', () => {
vueQueryState.value = dehydrate(queryClient)
})
}
if (import.meta.client && vueQueryState.value !== null) {
hydrate(queryClient, vueQueryState.value)
}
})
```
Now you are ready to prefetch some data in your pages with `onServerPrefetch`.
- Prefetch all the queries that you need with `queryClient.query`, `queryClient.infiniteQuery`, or `suspense`
```ts
export default defineComponent({
setup() {
const queryClient = useQueryClient()
const { data } = useQuery({
queryKey: ['test'],
queryFn: fetcher,
})
onServerPrefetch(async () => {
await suspense()
})
return { data }
},
})
```
### Nuxt 2
First create `vue-query.js` file in your `plugins` directory with the following content:
```js
import Vue from 'vue'
import { VueQueryPlugin, QueryClient, hydrate } from '@tanstack/vue-query'
export default (context) => {
// Modify your Vue Query global settings here
const queryClient = new QueryClient({
defaultOptions: { queries: { staleTime: 5000 } },
})
if (process.server) {
context.ssrContext.VueQuery = queryClient
}
if (process.client) {
Vue.use(VueQueryPlugin, { queryClient })
if (context.nuxtState && context.nuxtState.vueQueryState) {
hydrate(queryClient, context.nuxtState.vueQueryState)
}
}
}
```
Add this plugin to your `nuxt.config.js`
```js
module.exports = {
...
plugins: ['~/plugins/vue-query.js'],
}
```
Now you are ready to prefetch some data in your pages with `onServerPrefetch`.
- Use `useContext` to get nuxt context
- Use `useQueryClient` to get server-side instance of `queryClient`
- Prefetch all the queries that you need with `queryClient.query`, `queryClient.infiniteQuery`, or `suspense`
- Dehydrate `queryClient` to the `nuxtContext`
```vue
// pages/todos.vue
<template>
<div>
<button @click="refetch">Refetch</button>
<p>{{ data }}</p>
</div>
</template>
<script lang="ts">
import {
defineComponent,
onServerPrefetch,
useContext,
} from '@nuxtjs/composition-api'
import { useQuery, useQueryClient, dehydrate } from '@tanstack/vue-query'
export default defineComponent({
setup() {
// Get QueryClient either from SSR context, or Vue context
const { ssrContext } = useContext()
// Make sure to provide `queryClient` as a second parameter to `useQuery` calls
const queryClient =
(ssrContext != null && ssrContext.VueQuery) || useQueryClient()
// This will be prefetched and sent from the server
const { data, refetch, suspense } = useQuery(
{
queryKey: ['todos'],
queryFn: getTodos,
},
queryClient,
)
// This won't be prefetched, it will start fetching on client side
const { data2 } = useQuery(
{
queryKey: ['todos2'],
queryFn: getTodos,
},
queryClient,
)
onServerPrefetch(async () => {
await suspense()
ssrContext.nuxt.vueQueryState = dehydrate(queryClient)
})
return {
refetch,
data,
}
},
})
</script>
```
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
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`:
```html
<!-- MyComponent.vue -->
<template>
<div>
<button @click="refetch">Refetch</button>
<p>{{ data }}</p>
</div>
</template>
<script setup>
import { useQuery } from '@tanstack/vue-query'
import { onServerPrefetch } from 'vue'
// This will be prefetched and sent from the server
const { refetch, data, suspense } = useQuery({
queryKey: ['todos'],
queryFn: getTodos,
})
onServerPrefetch(suspense)
</script>
```
## Tips, Tricks and Caveats
### Only successful queries are included in dehydration
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 `queryClient.query` and catch any errors to handle those manually.
### Staleness is measured from when the query was fetched on the server
A query is considered stale depending on when it was `dataUpdatedAt`. A caveat here is that the server needs to have the correct time for this to work properly, but UTC time is used, so timezones do not factor into this.
Because `staleTime` defaults to `0`, queries will be refetched in the background on page load by default. You might want to use a higher `staleTime` to avoid this double fetching, especially if you don't cache your markup.
This refetching of stale queries is a perfect match when caching markup in a CDN! You can set the cache time of the page itself decently high to avoid having to re-render pages on the server, but configure the `staleTime` of the queries lower to make sure data is refetched in the background as soon as a user visits the page. Maybe you want to cache the pages for a week, but refetch the data automatically on page load if it's older than a day?
### High memory consumption on server
In case you are creating the `QueryClient` for every request, Vue Query creates the isolated cache for this client, which is preserved in memory for the `gcTime` period. That may lead to high memory consumption on server in case of high number of requests during that period.
On the server, `gcTime` defaults to `Infinity` which disables manual garbage collection and will automatically clear memory once a request has finished. If you are explicitly setting a non-Infinity `gcTime` then you will be responsible for clearing the cache early.
To clear the cache after it is not needed and to lower memory consumption, you can add a call to [`queryClient.clear()`](../reference/classes/QueryClient.md#clear) after the request is handled and dehydrated state has been sent to the client.
## `dehydrate`/`hydrate` options
`dehydrate` and `hydrate` are re-exported from `@tanstack/query-core`, so they're available directly from `@tanstack/vue-query` — as used throughout this guide.
`dehydrate(client, options?)` accepts a `DehydrateOptions` object:
- `shouldDehydrateMutation: (mutation) => boolean` — whether to dehydrate a given mutation. Called for each mutation in the cache; defaults to only including paused mutations. To extend the default behavior instead of replacing it, import and call `defaultShouldDehydrateMutation` as part of your return statement.
- `shouldDehydrateQuery: (query) => boolean` — whether to dehydrate a given query. Called for each query in the cache; defaults to only including successful queries (see [above](#only-successful-queries-are-included-in-dehydration)). Extend it the same way, with `defaultShouldDehydrateQuery`.
- `serializeData?: (data) => any` — transforms (serializes) data during dehydration.
- `shouldRedactErrors?: (error) => boolean` — only applies to queries still `pending` at dehydration time, whose promise is dehydrated too: decides whether to redact the error if that promise later rejects. Defaults to redacting all such errors. Does **not** apply to `query.state.error` on already-settled queries — that error is included in the dehydrated state as-is, so sanitize it yourself via `shouldDehydrateQuery` if it may contain sensitive data.
`hydrate(client, dehydratedState, options?)` accepts a `HydrateOptions` object:
- `defaultOptions.mutations` — default mutation options for the hydrated mutations.
- `defaultOptions.queries` — default query options for the hydrated queries.
- `defaultOptions.deserializeData?: (data) => any` — transforms (deserializes) data before it's put into the cache.
One caveat with `hydrate`: if a query being hydrated already exists in the cache, it's only overwritten when the incoming data is newer than what's already there — otherwise it's silently skipped.
Alternatively, you can set a smaller `gcTime`.

View File

@@ -0,0 +1,57 @@
---
id: suspense
title: Suspense (experimental)
---
> NOTE: Suspense mode for Vue Query is experimental, same as Vue's Suspense itself. These APIs WILL change and should not be used in production unless you lock both your Vue and Vue Query versions to patch-level versions that are compatible with each other.
Vue Query can also be used with Vue's new [Suspense](https://vuejs.org/guide/built-ins/suspense.html) API's.
To do that you need to wrap your suspendable component with `Suspense` component provided by Vue
```vue
<script setup>
import SuspendableComponent from './SuspendableComponent.vue'
</script>
<template>
<Suspense>
<template #default>
<SuspendableComponent />
</template>
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
</template>
```
And change your `setup` function in suspendable component to be `async`. Then you can use async `suspense` function that is provided by `vue-query`.
```vue
<script>
import { defineComponent } from 'vue'
import { useQuery } from '@tanstack/vue-query'
const todoFetcher = async () =>
await fetch('https://jsonplaceholder.cypress.io/todos').then((response) =>
response.json(),
)
export default defineComponent({
name: 'SuspendableComponent',
async setup() {
const { data, suspense } = useQuery({
queryKey: ['todos'],
queryFn: todoFetcher,
})
await suspense()
return { data }
},
})
</script>
```
## Fetch-on-render vs Render-as-you-fetch
Out of the box, Vue Query in `suspense` mode works really well as a **Fetch-on-render** solution with no additional configuration. This means that when your components attempt to mount, they will trigger query fetching and suspend, but only once you have imported them and mounted them. If you want to take it to the next level and implement a **Render-as-you-fetch** model, we recommend implementing [Prefetching](./prefetching) on routing callbacks and/or user interactions events to start loading queries before they are mounted and hopefully even before you start importing or mounting their parent components.

View File

@@ -0,0 +1,4 @@
---
id: testing
title: Testing
---

View File

@@ -0,0 +1,5 @@
---
id: updates-from-mutation-responses
title: Updates from Mutation Responses
ref: docs/framework/react/guides/updates-from-mutation-responses.md
---

View File

@@ -0,0 +1,25 @@
---
id: window-focus-refetching
title: Window Focus Refetching
ref: docs/framework/react/guides/window-focus-refetching.md
replace: { '@tanstack/react-query': '@tanstack/vue-query' }
---
[//]: # 'Example'
```js
const vueQueryPluginOptions: VueQueryPluginOptions = {
queryClientConfig: {
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
},
},
},
}
app.use(VueQueryPlugin, vueQueryPluginOptions)
```
[//]: # 'Example'
[//]: # 'ReactNative'
[//]: # 'ReactNative'