From fb914468570e640493e23021845cd831ed58a297 Mon Sep 17 00:00:00 2001 From: Damian Osipiuk Date: Sun, 12 May 2024 00:31:28 +0200 Subject: [PATCH] docs(vue-query): add missing links and pages (#7414) --- docs/config.json | 43 +++++- docs/framework/react/guides/mutations.md | 3 +- docs/framework/react/guides/query-options.md | 2 +- docs/framework/react/typescript.md | 4 - .../vue/community/community-projects.md | 11 +- .../lukemorales-query-key-factory.md | 10 -- .../framework/vue/guides/disabling-queries.md | 6 +- .../vue/guides/optimistic-updates.md | 99 +------------ docs/framework/vue/guides/query-options.md | 1 + docs/framework/vue/installation.md | 2 - .../vue/plugins/broadcastQueryClient.md | 6 + docs/framework/vue/plugins/createPersister.md | 131 ++++++++++++++++++ docs/framework/vue/reference/hydration.md | 6 +- .../vue/reference/infiniteQueryOptions.md | 5 + docs/framework/vue/typescript.md | 8 +- 15 files changed, 205 insertions(+), 132 deletions(-) delete mode 100644 docs/framework/vue/community/lukemorales-query-key-factory.md create mode 100644 docs/framework/vue/plugins/broadcastQueryClient.md create mode 100644 docs/framework/vue/plugins/createPersister.md create mode 100644 docs/framework/vue/reference/infiniteQueryOptions.md diff --git a/docs/config.json b/docs/config.json index 01ddf6e8fe..b75eab240e 100644 --- a/docs/config.json +++ b/docs/config.json @@ -325,6 +325,10 @@ "label": "Query Functions", "to": "framework/vue/guides/query-functions" }, + { + "label": "Query Options", + "to": "framework/vue/guides/query-options" + }, { "label": "Network Mode", "to": "framework/vue/guides/network-mode" @@ -369,10 +373,6 @@ "label": "Placeholder Query Data", "to": "framework/vue/guides/placeholder-query-data" }, - { - "label": "Prefetching", - "to": "framework/vue/guides/prefetching" - }, { "label": "Mutations", "to": "framework/vue/guides/mutations" @@ -386,7 +386,7 @@ "to": "framework/vue/guides/invalidations-from-mutations" }, { - "label": "Updates from Mutation", + "label": "Updates from Mutation Responses", "to": "framework/vue/guides/updates-from-mutation-responses" }, { @@ -405,6 +405,10 @@ "label": "Filters", "to": "framework/vue/guides/filters" }, + { + "label": "Prefetching", + "to": "framework/vue/guides/prefetching" + }, { "label": "SSR & Nuxt", "to": "framework/vue/guides/ssr" @@ -691,10 +695,22 @@ "label": "useIsMutating", "to": "framework/vue/reference/useIsMutating" }, + { + "label": "useMutationState", + "to": "framework/vue/reference/useMutationState" + }, { "label": "useQueryClient", "to": "framework/vue/reference/useQueryClient" }, + { + "label": "queryOptions", + "to": "framework/vue/reference/queryOptions" + }, + { + "label": "infiniteQueryOptions", + "to": "framework/vue/reference/infiniteQueryOptions" + }, { "label": "hydration", "to": "framework/vue/reference/hydration" @@ -758,8 +774,8 @@ "to": "framework/vue/community/tkdodos-blog" }, { - "label": "Query Key Factory", - "to": "framework/vue/community/lukemorales-query-key-factory" + "label": "Community Projects", + "to": "framework/vue/community/community-projects" } ] } @@ -1000,6 +1016,19 @@ "to": "framework/react/plugins/createPersister" } ] + }, + { + "label": "vue", + "children": [ + { + "label": "broadcastQueryClient (Experimental)", + "to": "framework/vue/plugins/broadcastQueryClient" + }, + { + "label": "createPersister (Experimental)", + "to": "framework/vue/plugins/createPersister" + } + ] } ] } diff --git a/docs/framework/react/guides/mutations.md b/docs/framework/react/guides/mutations.md index 200978a842..7208aee74d 100644 --- a/docs/framework/react/guides/mutations.md +++ b/docs/framework/react/guides/mutations.md @@ -388,8 +388,6 @@ export default function App() { We also have an extensive [offline example](../../examples/offline) that covers both queries and mutations. -[//]: # 'Materials' - ## Mutation Scopes Per default, all mutations run in parallel - even if you invoke `.mutate()` of the same mutation multiple times. Mutations can be given a `scope` with an `id` to avoid that. All mutations with the same `scope.id` will run in serial, which means when they are triggered, they will start in `isPaused: true` state if there is already a mutation for that scope in progress. They will be put into a queue and will automatically resume once their time in the queue has come. @@ -406,6 +404,7 @@ const mutation = useMutation({ ``` [//]: # 'ExampleScopes' +[//]: # 'Materials' ## Further reading diff --git a/docs/framework/react/guides/query-options.md b/docs/framework/react/guides/query-options.md index e9e702d294..8b9c4c1c3e 100644 --- a/docs/framework/react/guides/query-options.md +++ b/docs/framework/react/guides/query-options.md @@ -3,7 +3,7 @@ id: query-options title: Query Options --- -One of the best ways to share `queryKey` and `queryFn` between multiple places, yet keep them co-located to one another, is to use the `queryOptions` helper. At runtime, this helper just returns whatever you pass into it, but it has a lot of advantages when using it [with TypeScript](../../../react/typescript#typing-query-options). You can define all possible options for a query in one place, and you'll also get type inference and type safety for all of them. +One of the best ways to share `queryKey` and `queryFn` between multiple places, yet keep them co-located to one another, is to use the `queryOptions` helper. At runtime, this helper just returns whatever you pass into it, but it has a lot of advantages when using it [with TypeScript](../typescript#typing-query-options). You can define all possible options for a query in one place, and you'll also get type inference and type safety for all of them. [//]: # 'Example1' diff --git a/docs/framework/react/typescript.md b/docs/framework/react/typescript.md index a5a7726064..6832db00cd 100644 --- a/docs/framework/react/typescript.md +++ b/docs/framework/react/typescript.md @@ -86,7 +86,6 @@ if (isSuccess) { The type for error defaults to `Error`, because that is what most users expect. -[//]: # 'TypingError' [//]: # 'TypingError' ```tsx @@ -153,8 +152,6 @@ const { error } = useQuery({ queryKey: ['groups'], queryFn: fetchGroups }) ## Typing meta -[//]: # 'RegisterMetaType' - ### Registering global Meta Similarly to registering a [global error type](#registering-a-global-error) you can also register a global `Meta` type. This ensures the optional `meta` field on [queries](../reference/useQuery) and [mutations](../reference/useMutation) stays consistent and is type-safe. Note that the registered type must extend `Record` so that `meta` remains an object. @@ -174,7 +171,6 @@ declare module '@tanstack/react-query' { } ``` -[//]: # 'RegisterMetaType' [//]: # 'TypingMeta' [//]: # 'TypingQueryOptions' diff --git a/docs/framework/vue/community/community-projects.md b/docs/framework/vue/community/community-projects.md index 86e065967f..2950d2a7c6 100644 --- a/docs/framework/vue/community/community-projects.md +++ b/docs/framework/vue/community/community-projects.md @@ -1,5 +1,14 @@ --- id: community-projects title: Community Projects -ref: docs/framework/react/community/community-projects.md --- + +There are lots of community projects that build on top of Vue Query and use it to provide additional functionality or enhanced developer experience. Projects are listed in alphabetical order. If you have a project that you would like to add to this list, please open a PR! + +> Please note that these projects are entirely community maintained. If you have questions about these projects, please reach out to the project maintainers. + +## Query Key factory + +A library for creating typesafe standardized query keys, useful for cache management in `@tanstack/query` + +Link: https://github.com/lukemorales/query-key-factory diff --git a/docs/framework/vue/community/lukemorales-query-key-factory.md b/docs/framework/vue/community/lukemorales-query-key-factory.md deleted file mode 100644 index 800a06f5b5..0000000000 --- a/docs/framework/vue/community/lukemorales-query-key-factory.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -id: query-key-factory -title: Query Key Factory ---- - -## Query Key Factory - -A library for creating typesafe standardized query keys, useful for cache management in `@tanstack/query` - -Link: https://github.com/lukemorales/query-key-factory diff --git a/docs/framework/vue/guides/disabling-queries.md b/docs/framework/vue/guides/disabling-queries.md index bd0de7532f..11c0e1c8f2 100644 --- a/docs/framework/vue/guides/disabling-queries.md +++ b/docs/framework/vue/guides/disabling-queries.md @@ -60,11 +60,13 @@ const { data } = useQuery({ import { useQuery, skipToken } from '@tanstack/vue-query' const filter = ref('') -const isEnabled = computed(() => !!filter.value) +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: filter ? () => fetchTodos(filter) : skipToken, + queryFn: queryFn, }) diff --git a/docs/framework/vue/guides/optimistic-updates.md b/docs/framework/vue/guides/optimistic-updates.md index 57a25aeba8..be3d9cf549 100644 --- a/docs/framework/vue/guides/optimistic-updates.md +++ b/docs/framework/vue/guides/optimistic-updates.md @@ -2,102 +2,5 @@ id: optimistic-updates title: Optimistic Updates ref: docs/framework/react/guides/optimistic-updates.md +replace: { 'React': 'Vue' } --- - -When you optimistically update your state before performing a mutation, there is a chance that the mutation will fail. In most of these failure cases, you can just trigger a refetch for your optimistic queries to revert them to their true server state. In some circumstances though, refetching may not work correctly and the mutation error could represent some type of server issue that won't make it possible to refetch. In this event, you can instead choose to rollback your update. - -To do this, `useMutation`'s `onMutate` handler option allows you to return a value that will later be passed to both `onError` and `onSettled` handlers as the last argument. In most cases, it is most useful to pass a rollback function. - -## Updating a list of todos when adding a new todo - -[//]: # 'Example' - -```tsx -const queryClient = useQueryClient() - -useMutation({ - mutationFn: updateTodo, - // When mutate is called: - onMutate: async (newTodo) => { - // Cancel any outgoing refetches - // (so they don't overwrite our optimistic update) - await queryClient.cancelQueries({ queryKey: ['todos'] }) - - // Snapshot the previous value - const previousTodos = queryClient.getQueryData(['todos']) - - // Optimistically update to the new value - queryClient.setQueryData(['todos'], (old) => [...old, newTodo]) - - // Return a context object with the snapshotted value - return { previousTodos } - }, - // If the mutation fails, - // use the context returned from onMutate to roll back - onError: (err, newTodo, context) => { - queryClient.setQueryData(['todos'], context.previousTodos) - }, - // Always refetch after error or success: - onSettled: () => { - queryClient.invalidateQueries({ queryKey: ['todos'] }) - }, -}) -``` - -[//]: # 'Example' - -## Updating a single todo - -[//]: # 'Example2' - -```tsx -useMutation({ - mutationFn: updateTodo, - // When mutate is called: - onMutate: async (newTodo) => { - // Cancel any outgoing refetches - // (so they don't overwrite our optimistic update) - await queryClient.cancelQueries({ queryKey: ['todos', newTodo.id] }) - - // Snapshot the previous value - const previousTodo = queryClient.getQueryData(['todos', newTodo.id]) - - // Optimistically update to the new value - queryClient.setQueryData(['todos', newTodo.id], newTodo) - - // Return a context with the previous and new todo - return { previousTodo, newTodo } - }, - // If the mutation fails, use the context we returned above - onError: (err, newTodo, context) => { - queryClient.setQueryData( - ['todos', context.newTodo.id], - context.previousTodo, - ) - }, - // Always refetch after error or success: - onSettled: (newTodo) => { - queryClient.invalidateQueries({ queryKey: ['todos', newTodo.id] }) - }, -}) -``` - -[//]: # 'Example2' - -You can also use the `onSettled` function in place of the separate `onError` and `onSuccess` handlers if you wish: - -[//]: # 'Example3' - -```tsx -useMutation({ - mutationFn: updateTodo, - // ... - onSettled: (newTodo, error, variables, context) => { - if (error) { - // do something - } - }, -}) -``` - -[//]: # 'Example3' diff --git a/docs/framework/vue/guides/query-options.md b/docs/framework/vue/guides/query-options.md index 83bcf1e853..f2bb35eac6 100644 --- a/docs/framework/vue/guides/query-options.md +++ b/docs/framework/vue/guides/query-options.md @@ -2,4 +2,5 @@ id: query-options title: Query Options ref: docs/framework/react/guides/query-options.md +replace: { '@tanstack/react-query': '@tanstack/vue-query' } --- diff --git a/docs/framework/vue/installation.md b/docs/framework/vue/installation.md index 1386b79fc4..b32146f2ed 100644 --- a/docs/framework/vue/installation.md +++ b/docs/framework/vue/installation.md @@ -5,8 +5,6 @@ title: Installation You can install Vue Query via [NPM](https://npmjs.com). -> v5 is currently available as a release-candidate. We don't anticipate any major API changes from here on out. We encourage you to try it out and report any issues you find. - ### NPM ```bash diff --git a/docs/framework/vue/plugins/broadcastQueryClient.md b/docs/framework/vue/plugins/broadcastQueryClient.md new file mode 100644 index 0000000000..a4cfe04bc6 --- /dev/null +++ b/docs/framework/vue/plugins/broadcastQueryClient.md @@ -0,0 +1,6 @@ +--- +id: broadcastQueryClient +title: broadcastQueryClient (Experimental) +ref: docs/framework/react/plugins/broadcastQueryClient.md +replace: { 'react-query': 'vue-query' } +--- diff --git a/docs/framework/vue/plugins/createPersister.md b/docs/framework/vue/plugins/createPersister.md new file mode 100644 index 0000000000..b2f9773ca8 --- /dev/null +++ b/docs/framework/vue/plugins/createPersister.md @@ -0,0 +1,131 @@ +--- +id: createPersister +title: experimental_createPersister +--- + +## Installation + +This utility comes as a separate package and is available under the `'@tanstack/query-persist-client-core'` import. + +```bash +npm install @tanstack/query-persist-client-core +``` + +or + +```bash +pnpm add @tanstack/query-persist-client-core +``` + +or + +```bash +yarn add @tanstack/query-persist-client-core +``` + +or + +```bash +bun add @tanstack/query-persist-client-core +``` + +## Usage + +- Import the `experimental_createPersister` function +- Create a new `experimental_createPersister` + - you can pass any `storage` to it that adheres to the `AsyncStorage` or `Storage` interface +- Pass that `persister` as an option to your Query. This can be done either by passing it to the `defaultOptions` of the `QueryClient` or to any `useQuery` hook instance. + - If you pass this `persister` as `defaultOptions`, all queries will be persisted to the provided `storage`. You can additionally narrow this down by passing `filters`. In contrast to the `persistClient` plugin, this will not persist the whole query client as a single item, but each query separately. As a key, the query hash is used. + - If you provide this `persister` to a single `useQuery` hook, only this Query will be persisted. + +This way, you do not need to store whole `QueryClient`, but choose what is worth to be persisted in your application. Each query is lazily restored (when the Query is first used) and persisted (after each run of the `queryFn`), so it does not need to be throttled. `staleTime` is also respected after restoring the Query, so if data is considered `stale`, it will be refetched immediately after restoring. If data is `fresh`, the `queryFn` will not run. + +Garbage collecting a Query from memory **does not** affect the persisted data. That means Queries can be kept in memory for a shorter period of time to be more **memory efficient**. If they are used the next time, they will just be restored from the persistent storage again. + +```tsx +import { QueryClient } from '@tanstack/vue-query' +import { experimental_createPersister } from '@tanstack/query-persist-client-core' + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + gcTime: 1000 * 30, // 30 seconds + persister: experimental_createPersister({ + storage: localStorage, + maxAge: 1000 * 60 * 60 * 12, // 12 hours + }), + }, + }, +}) +``` + +### Adapted defaults + +The `createPersister` plugin technically wraps the `queryFn`, so it doesn't restore if the `queryFn` doesn't run. In that way, it acts as a caching layer between the Query and the network. Thus, the `networkMode` defaults to `'offlineFirst'` when a persister is used, so that restoring from the persistent storage can also happen even if there is no network connection. + +## API + +### `experimental_createPersister` + +```tsx +experimental_createPersister(options: StoragePersisterOptions) +``` + +#### `Options` + +```tsx +export interface StoragePersisterOptions { + /** The storage client used for setting and retrieving items from cache. + * For SSR pass in `undefined`. + */ + storage: AsyncStorage | Storage | undefined | null + /** + * How to serialize the data to storage. + * @default `JSON.stringify` + */ + serialize?: (persistedQuery: PersistedQuery) => string + /** + * How to deserialize the data from storage. + * @default `JSON.parse` + */ + deserialize?: (cachedString: string) => PersistedQuery + /** + * A unique string that can be used to forcefully invalidate existing caches, + * if they do not share the same buster string + */ + buster?: string + /** + * The max-allowed age of the cache in milliseconds. + * If a persisted cache is found that is older than this + * time, it will be discarded + * @default 24 hours + */ + maxAge?: number + /** + * Prefix to be used for storage key. + * Storage key is a combination of prefix and query hash in a form of `prefix-queryHash`. + */ + prefix?: string + /** + * Filters to narrow down which Queries should be persisted. + */ + filters?: QueryFilters +} + +interface AsyncStorage { + getItem: (key: string) => Promise + setItem: (key: string, value: string) => Promise + removeItem: (key: string) => Promise +} +``` + +The default options are: + +```tsx +{ + prefix = 'tanstack-query', + maxAge = 1000 * 60 * 60 * 24, + serialize = JSON.stringify, + deserialize = JSON.parse, +} +``` diff --git a/docs/framework/vue/reference/hydration.md b/docs/framework/vue/reference/hydration.md index 74e94770d6..f4955994b2 100644 --- a/docs/framework/vue/reference/hydration.md +++ b/docs/framework/vue/reference/hydration.md @@ -5,7 +5,5 @@ ref: docs/framework/react/reference/hydration.md replace: { '@tanstack/react-query': '@tanstack/vue-query' } --- -[//]: # 'useHydrate' -[//]: # 'useHydrate' -[//]: # 'Hydrate' -[//]: # 'Hydrate' +[//]: # 'HydrationBoundary' +[//]: # 'HydrationBoundary' diff --git a/docs/framework/vue/reference/infiniteQueryOptions.md b/docs/framework/vue/reference/infiniteQueryOptions.md new file mode 100644 index 0000000000..fa4b38aae9 --- /dev/null +++ b/docs/framework/vue/reference/infiniteQueryOptions.md @@ -0,0 +1,5 @@ +--- +id: infiniteQueryOptions +title: infiniteQueryOptions +ref: docs/framework/react/reference/infiniteQueryOptions.md +--- diff --git a/docs/framework/vue/typescript.md b/docs/framework/vue/typescript.md index 71b354a0e4..731c276a24 100644 --- a/docs/framework/vue/typescript.md +++ b/docs/framework/vue/typescript.md @@ -3,7 +3,11 @@ id: typescript title: TypeScript ref: docs/framework/react/typescript.md replace: - { 'React': 'Vue', 'react-query package version': 'vue-query package version' } + { + 'React': 'Vue', + '@tanstack/react-query': '@tanstack/vue-query', + 'react-query package version': 'vue-query package version', + } --- [//]: # 'TypeInference1' @@ -86,6 +90,8 @@ if (error.value instanceof Error) { [//]: # 'TypingError3' [//]: # 'RegisterErrorType' [//]: # 'RegisterErrorType' +[//]: # 'TypingMeta' +[//]: # 'TypingMeta' [//]: # 'TypingQueryOptions' [//]: # 'TypingQueryOptions' [//]: # 'Materials'