snapshot of https://github.com/TanStack/query.git (main, 2026-09-16; blobs > 131072B omitted)
This commit is contained in:
103
docs/framework/react/plugins/broadcastQueryClient.md
Normal file
103
docs/framework/react/plugins/broadcastQueryClient.md
Normal file
@@ -0,0 +1,103 @@
|
||||
---
|
||||
id: broadcastQueryClient
|
||||
title: broadcastQueryClient (Experimental)
|
||||
---
|
||||
|
||||
> VERY IMPORTANT: This utility is currently in an experimental stage. This means that breaking changes will happen in minor AND patch releases. Use at your own risk. If you choose to rely on this in production in an experimental stage, please lock your version to a patch-level version to avoid unexpected breakages.
|
||||
|
||||
`broadcastQueryClient` is a utility for broadcasting and syncing the state of your queryClient between browser tabs/windows with the same origin.
|
||||
|
||||
## Installation
|
||||
|
||||
This utility comes as a separate package and is available under the `'@tanstack/query-broadcast-client-experimental'` import.
|
||||
|
||||
## Usage
|
||||
|
||||
Import the `broadcastQueryClient` function, and pass it your `QueryClient` instance, and optionally, set a `broadcastChannel`.
|
||||
|
||||
```tsx
|
||||
import { broadcastQueryClient } from '@tanstack/query-broadcast-client-experimental'
|
||||
|
||||
const queryClient = new QueryClient()
|
||||
|
||||
broadcastQueryClient({
|
||||
queryClient,
|
||||
broadcastChannel: 'my-app',
|
||||
})
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### `broadcastQueryClient`
|
||||
|
||||
Pass this function a `QueryClient` instance and optionally, a `broadcastChannel`.
|
||||
|
||||
```tsx
|
||||
broadcastQueryClient({ queryClient, broadcastChannel })
|
||||
```
|
||||
|
||||
### `Options`
|
||||
|
||||
An object of options:
|
||||
|
||||
```tsx
|
||||
interface BroadcastQueryClientOptions {
|
||||
/** The QueryClient to sync */
|
||||
queryClient: QueryClient
|
||||
/** This is the unique channel name that will be used
|
||||
* to communicate between tabs and windows */
|
||||
broadcastChannel?: string
|
||||
/** Options for the BroadcastChannel API */
|
||||
options?: BroadcastChannelOptions
|
||||
/**
|
||||
* Called when a query event fails to broadcast to other tabs — most
|
||||
* commonly because the query's data, error, or key contains a value the
|
||||
* structured-clone algorithm cannot serialize (e.g. `ReadableStream`,
|
||||
* `File`, functions, Vue `reactive` proxies).
|
||||
*
|
||||
* If omitted, a `console.warn` is emitted in development so failures
|
||||
* are never entirely silent. May return a `Promise`; any rejection is
|
||||
* caught internally.
|
||||
*/
|
||||
onBroadcastError?: (
|
||||
error: unknown,
|
||||
event: BroadcastErrorEvent,
|
||||
) => void | Promise<void>
|
||||
}
|
||||
|
||||
interface BroadcastErrorEvent {
|
||||
type: 'updated' | 'removed' | 'added'
|
||||
queryHash: string
|
||||
queryKey: QueryKey
|
||||
}
|
||||
```
|
||||
|
||||
The default options are:
|
||||
|
||||
```tsx
|
||||
{
|
||||
broadcastChannel = 'tanstack-query',
|
||||
}
|
||||
```
|
||||
|
||||
## Handling broadcast errors
|
||||
|
||||
If your cache can hold values that are not structured-cloneable — such as `ReadableStream` (from `Response.body`, streaming APIs, or AI SDKs), `File`, functions, or framework proxies like Vue `reactive` — the underlying `BroadcastChannel.postMessage` call will reject for that query. Cross-tab sync is skipped for that query; the rest of the cache continues to broadcast normally.
|
||||
|
||||
By default, a `console.warn` is emitted in development so failures are never silent. Provide `onBroadcastError` to route failures to your own error tracker:
|
||||
|
||||
```tsx
|
||||
import * as Sentry from '@sentry/browser'
|
||||
import { broadcastQueryClient } from '@tanstack/query-broadcast-client-experimental'
|
||||
|
||||
broadcastQueryClient({
|
||||
queryClient,
|
||||
broadcastChannel: 'my-app',
|
||||
onBroadcastError: (error, event) => {
|
||||
Sentry.captureException(error, {
|
||||
tags: { broadcastEvent: event.type },
|
||||
extra: { queryHash: event.queryHash, queryKey: event.queryKey },
|
||||
})
|
||||
},
|
||||
})
|
||||
```
|
||||
122
docs/framework/react/plugins/createAsyncStoragePersister.md
Normal file
122
docs/framework/react/plugins/createAsyncStoragePersister.md
Normal file
@@ -0,0 +1,122 @@
|
||||
---
|
||||
id: createAsyncStoragePersister
|
||||
title: createAsyncStoragePersister
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
This utility comes as a separate package and is available under the `'@tanstack/query-async-storage-persister'` import.
|
||||
|
||||
```bash
|
||||
npm install @tanstack/query-async-storage-persister @tanstack/react-query-persist-client
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```bash
|
||||
pnpm add @tanstack/query-async-storage-persister @tanstack/react-query-persist-client
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```bash
|
||||
yarn add @tanstack/query-async-storage-persister @tanstack/react-query-persist-client
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```bash
|
||||
bun add @tanstack/query-async-storage-persister @tanstack/react-query-persist-client
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
- Import the `createAsyncStoragePersister` function
|
||||
- Create a new asyncStoragePersister
|
||||
- you can pass any `storage` to it that adheres to the `AsyncStorage` interface - the example below uses the async-storage from React Native.
|
||||
- storages that read and write synchronously, like `window.localstorage`, also adhere to the `AsyncStorage` interface and can therefore also be used with `createAsyncStoragePersister`.
|
||||
- Wrap your app by using [`PersistQueryClientProvider`](./persistQueryClient.md#persistqueryclientprovider) component.
|
||||
|
||||
```tsx
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage'
|
||||
import { QueryClient } from '@tanstack/react-query'
|
||||
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client'
|
||||
import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister'
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
gcTime: 1000 * 60 * 60 * 24, // 24 hours
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const asyncStoragePersister = createAsyncStoragePersister({
|
||||
storage: AsyncStorage,
|
||||
})
|
||||
|
||||
const Root = () => (
|
||||
<PersistQueryClientProvider
|
||||
client={queryClient}
|
||||
persistOptions={{ persister: asyncStoragePersister }}
|
||||
>
|
||||
<App />
|
||||
</PersistQueryClientProvider>
|
||||
)
|
||||
|
||||
export default Root
|
||||
```
|
||||
|
||||
## Retries
|
||||
|
||||
Retries work the same as for a [SyncStoragePersister](./createSyncStoragePersister.md), except that they can also be asynchronous. You can also use all the predefined retry handlers.
|
||||
|
||||
## API
|
||||
|
||||
### `createAsyncStoragePersister`
|
||||
|
||||
Call this function to create an asyncStoragePersister that you can use later with `persistQueryClient`.
|
||||
|
||||
```tsx
|
||||
createAsyncStoragePersister(options: CreateAsyncStoragePersisterOptions)
|
||||
```
|
||||
|
||||
### `Options`
|
||||
|
||||
```tsx
|
||||
interface CreateAsyncStoragePersisterOptions {
|
||||
/** The storage client used for setting an retrieving items from cache */
|
||||
storage: AsyncStorage | undefined | null
|
||||
/** The key to use when storing the cache to localStorage */
|
||||
key?: string
|
||||
/** To avoid localStorage spamming,
|
||||
* pass a time in ms to throttle saving the cache to disk */
|
||||
throttleTime?: number
|
||||
/** How to serialize the data to storage */
|
||||
serialize?: (client: PersistedClient) => MaybePromise<string>
|
||||
/** How to deserialize the data from storage */
|
||||
deserialize?: (cachedString: string) => MaybePromise<PersistedClient>
|
||||
/** How to retry persistence on error **/
|
||||
retry?: AsyncPersistRetryer
|
||||
}
|
||||
|
||||
interface AsyncStorage<TStorageValue = string> {
|
||||
getItem: (key: string) => MaybePromise<TStorageValue | undefined | null>
|
||||
setItem: (key: string, value: TStorageValue) => MaybePromise<unknown>
|
||||
removeItem: (key: string) => MaybePromise<void>
|
||||
entries?: () => MaybePromise<Array<[key: string, value: TStorageValue]>>
|
||||
}
|
||||
```
|
||||
|
||||
The `serialize` and `deserialize` callbacks can return their results synchronously or asynchronously as promises.
|
||||
|
||||
The default options are:
|
||||
|
||||
```tsx
|
||||
{
|
||||
key = `REACT_QUERY_OFFLINE_CACHE`,
|
||||
throttleTime = 1000,
|
||||
serialize = JSON.stringify,
|
||||
deserialize = JSON.parse,
|
||||
}
|
||||
```
|
||||
218
docs/framework/react/plugins/createPersister.md
Normal file
218
docs/framework/react/plugins/createPersister.md
Normal file
@@ -0,0 +1,218 @@
|
||||
---
|
||||
id: createPersister
|
||||
title: experimental_createQueryPersister
|
||||
---
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
> Note: This util is also included in the `@tanstack/react-query-persist-client` package, so you do not need to install it separately if you are using that package.
|
||||
|
||||
## Usage
|
||||
|
||||
- Import the `experimental_createQueryPersister` function
|
||||
- Create a new `experimental_createQueryPersister`
|
||||
- you can pass any `storage` to it that adheres to the `AsyncStorage` interface - the example below uses the async-storage from React Native.
|
||||
- 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.
|
||||
- Note: `queryClient.setQueryData()` operations are not persisted, this means that if you perform an optimistic update and refresh the page before the query has been invalidated, your changes to the query data will be lost. See https://github.com/TanStack/query/issues/6310
|
||||
|
||||
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 AsyncStorage from '@react-native-async-storage/async-storage'
|
||||
import { QueryClient } from '@tanstack/react-query'
|
||||
import { experimental_createQueryPersister } from '@tanstack/query-persist-client-core'
|
||||
|
||||
const persister = experimental_createQueryPersister({
|
||||
storage: AsyncStorage,
|
||||
maxAge: 1000 * 60 * 60 * 12, // 12 hours
|
||||
})
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
gcTime: 1000 * 30, // 30 seconds
|
||||
persister: persister.persisterFn,
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
## Additional utilities
|
||||
|
||||
Invoking `experimental_createQueryPersister` returns additional utilities in addition to `persisterFn` for easier implementation of userland functionalities.
|
||||
|
||||
### `persistQueryByKey(queryKey: QueryKey, queryClient: QueryClient): Promise<void>`
|
||||
|
||||
This function will persist `Query` to storage and key defined when creating persister.
|
||||
This utility might be used along `setQueryData` to persist optimistic update to storage without waiting for invalidation.
|
||||
|
||||
```tsx
|
||||
const persister = experimental_createQueryPersister({
|
||||
storage: AsyncStorage,
|
||||
maxAge: 1000 * 60 * 60 * 12, // 12 hours
|
||||
})
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
useMutation({
|
||||
mutationFn: updateTodo,
|
||||
onMutate: async (newTodo) => {
|
||||
...
|
||||
// Optimistically update to the new value
|
||||
queryClient.setQueryData(['todos'], (old) => [...old, newTodo])
|
||||
// And persist it to storage
|
||||
persister.persistQueryByKey(['todos'], queryClient)
|
||||
...
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### `retrieveQuery<T>(queryHash: string): Promise<T | undefined>`
|
||||
|
||||
This function would attempt to retrieve persisted query by `queryHash`.
|
||||
If `query` is `expired`, `busted` or `malformed` it would be removed from the storage instead, and `undefined` would be returned.
|
||||
|
||||
### `persisterGc(): Promise<void>`
|
||||
|
||||
This function can be used to sporadically clean up storage from `expired`, `busted` or `malformed` entries.
|
||||
|
||||
For this function to work, your storage must expose `entries` method that would return a `key-value tuple array`.
|
||||
For example `Object.entries(localStorage)` for `localStorage` or `entries` from `idb-keyval`.
|
||||
|
||||
### `restoreQueries(queryClient: QueryClient, filters): Promise<void>`
|
||||
|
||||
This function can be used to restore queries that are currently stored by persister.
|
||||
For example when your app is starting up in offline mode, or you want all or only specific data from previous session to be immediately available without intermediate `loading` state.
|
||||
|
||||
The filter object supports the following properties:
|
||||
|
||||
- `queryKey?: QueryKey`
|
||||
- Set this property to define a query key to match on.
|
||||
- `exact?: boolean`
|
||||
- If you don't want to search queries inclusively by query key, you can pass the `exact: true` option to return only the query with the exact query key you have passed.
|
||||
|
||||
For this function to work, your storage must expose `entries` method that would return a `key-value tuple array`.
|
||||
For example `Object.entries(localStorage)` for `localStorage` or `entries` from `idb-keyval`.
|
||||
|
||||
### `removeQueries(filters): Promise<void>`
|
||||
|
||||
When using `queryClient.removeQueries`, the data remains in the persister and needs to be removed separately.
|
||||
This function can be used to remove queries that are currently stored by persister.
|
||||
|
||||
The filter object supports the following properties:
|
||||
|
||||
- `queryKey?: QueryKey`
|
||||
- Set this property to define a query key to match on.
|
||||
- `exact?: boolean`
|
||||
- If you don't want to search queries inclusively by query key, you can pass the `exact: true` option to return only the query with the exact query key you have passed.
|
||||
|
||||
For this function to work, your storage must expose `entries` method that would return a `key-value tuple array`.
|
||||
For example `Object.entries(localStorage)` for `localStorage` or `entries` from `idb-keyval`.
|
||||
|
||||
## API
|
||||
|
||||
### `experimental_createQueryPersister`
|
||||
|
||||
```tsx
|
||||
experimental_createQueryPersister(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
|
||||
/**
|
||||
* If set to `true`, the query will refetch on successful query restoration if the data is stale.
|
||||
* If set to `false`, the query will not refetch on successful query restoration.
|
||||
* If set to `'always'`, the query will always refetch on successful query restoration.
|
||||
* Defaults to `true`.
|
||||
*/
|
||||
refetchOnRestore?: boolean | 'always'
|
||||
/**
|
||||
* Filters to narrow down which Queries should be persisted.
|
||||
*/
|
||||
filters?: QueryFilters
|
||||
}
|
||||
|
||||
interface AsyncStorage<TStorageValue = string> {
|
||||
getItem: (key: string) => MaybePromise<TStorageValue | undefined | null>
|
||||
setItem: (key: string, value: TStorageValue) => MaybePromise<unknown>
|
||||
removeItem: (key: string) => MaybePromise<void>
|
||||
entries?: () => MaybePromise<Array<[key: string, value: TStorageValue]>>
|
||||
}
|
||||
```
|
||||
|
||||
The default options are:
|
||||
|
||||
```tsx
|
||||
{
|
||||
prefix = 'tanstack-query',
|
||||
maxAge = 1000 * 60 * 60 * 24,
|
||||
serialize = JSON.stringify,
|
||||
deserialize = JSON.parse,
|
||||
refetchOnRestore = true,
|
||||
}
|
||||
```
|
||||
160
docs/framework/react/plugins/createSyncStoragePersister.md
Normal file
160
docs/framework/react/plugins/createSyncStoragePersister.md
Normal file
@@ -0,0 +1,160 @@
|
||||
---
|
||||
id: createSyncStoragePersister
|
||||
title: createSyncStoragePersister
|
||||
---
|
||||
|
||||
## Deprecated
|
||||
|
||||
This plugin is deprecated and will be removed in the next major version.
|
||||
You can simply use ['@tanstack/query-async-storage-persister'](./createAsyncStoragePersister.md) instead.
|
||||
|
||||
## Installation
|
||||
|
||||
This utility comes as a separate package and is available under the `'@tanstack/query-sync-storage-persister'` import.
|
||||
|
||||
```bash
|
||||
npm install @tanstack/query-sync-storage-persister @tanstack/react-query-persist-client
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```bash
|
||||
pnpm add @tanstack/query-sync-storage-persister @tanstack/react-query-persist-client
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```bash
|
||||
yarn add @tanstack/query-sync-storage-persister @tanstack/react-query-persist-client
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```bash
|
||||
bun add @tanstack/query-sync-storage-persister @tanstack/react-query-persist-client
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
- Import the `createSyncStoragePersister` function
|
||||
- Create a new syncStoragePersister
|
||||
- Pass it to the [`persistQueryClient`](./persistQueryClient.md) function
|
||||
|
||||
```tsx
|
||||
import { persistQueryClient } from '@tanstack/react-query-persist-client'
|
||||
import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister'
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
gcTime: 1000 * 60 * 60 * 24, // 24 hours
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const localStoragePersister = createSyncStoragePersister({
|
||||
storage: window.localStorage,
|
||||
})
|
||||
// const sessionStoragePersister = createSyncStoragePersister({ storage: window.sessionStorage })
|
||||
|
||||
persistQueryClient({
|
||||
queryClient,
|
||||
persister: localStoragePersister,
|
||||
})
|
||||
```
|
||||
|
||||
## Retries
|
||||
|
||||
Persistence can fail, e.g. if the size exceeds the available space on the storage. Errors can be handled gracefully by providing a `retry` function to the persister.
|
||||
|
||||
The retry function receives the `persistedClient` it tried to save, as well as the `error` and the `errorCount` as input. It is expected to return a _new_ `PersistedClient`, with which it tries to persist again. If _undefined_ is returned, there will be no further attempt to persist.
|
||||
|
||||
```tsx
|
||||
export type PersistRetryer = (props: {
|
||||
persistedClient: PersistedClient
|
||||
error: Error
|
||||
errorCount: number
|
||||
}) => PersistedClient | undefined
|
||||
```
|
||||
|
||||
### Predefined strategies
|
||||
|
||||
Per default, no retry will occur. You can use one of the predefined strategies to handle retries. They can be imported `from '@tanstack/react-query-persist-client'`:
|
||||
|
||||
- `removeOldestQuery`
|
||||
- will return a new `PersistedClient` with the oldest query removed.
|
||||
|
||||
```tsx
|
||||
const localStoragePersister = createSyncStoragePersister({
|
||||
storage: window.localStorage,
|
||||
retry: removeOldestQuery,
|
||||
})
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### `createSyncStoragePersister`
|
||||
|
||||
Call this function to create a syncStoragePersister that you can use later with `persistQueryClient`.
|
||||
|
||||
```tsx
|
||||
createSyncStoragePersister(options: CreateSyncStoragePersisterOptions)
|
||||
```
|
||||
|
||||
### `Options`
|
||||
|
||||
```tsx
|
||||
interface CreateSyncStoragePersisterOptions {
|
||||
/** The storage client used for setting an retrieving items from cache (window.localStorage or window.sessionStorage) */
|
||||
storage: Storage | undefined | null
|
||||
/** The key to use when storing the cache */
|
||||
key?: string
|
||||
/** To avoid spamming,
|
||||
* pass a time in ms to throttle saving the cache to disk */
|
||||
throttleTime?: number
|
||||
/** How to serialize the data to storage */
|
||||
serialize?: (client: PersistedClient) => string
|
||||
/** How to deserialize the data from storage */
|
||||
deserialize?: (cachedString: string) => PersistedClient
|
||||
/** How to retry persistence on error **/
|
||||
retry?: PersistRetryer
|
||||
}
|
||||
```
|
||||
|
||||
The default options are:
|
||||
|
||||
```tsx
|
||||
{
|
||||
key = `REACT_QUERY_OFFLINE_CACHE`,
|
||||
throttleTime = 1000,
|
||||
serialize = JSON.stringify,
|
||||
deserialize = JSON.parse,
|
||||
}
|
||||
```
|
||||
|
||||
#### `serialize` and `deserialize` options
|
||||
|
||||
There is a limit to the amount of data which can be stored in `localStorage`.
|
||||
If you need to store more data in `localStorage`, you can override the `serialize` and `deserialize` functions to compress and decompress the data using a library like [lz-string](https://github.com/pieroxy/lz-string/).
|
||||
|
||||
```tsx
|
||||
import { QueryClient } from '@tanstack/react-query'
|
||||
import { persistQueryClient } from '@tanstack/react-query-persist-client'
|
||||
import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister'
|
||||
|
||||
import { compress, decompress } from 'lz-string'
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: { queries: { staleTime: Infinity } },
|
||||
})
|
||||
|
||||
persistQueryClient({
|
||||
queryClient: queryClient,
|
||||
persister: createSyncStoragePersister({
|
||||
storage: window.localStorage,
|
||||
serialize: (data) => compress(JSON.stringify(data)),
|
||||
deserialize: (data) => JSON.parse(decompress(data)),
|
||||
}),
|
||||
maxAge: Infinity,
|
||||
})
|
||||
```
|
||||
292
docs/framework/react/plugins/persistQueryClient.md
Normal file
292
docs/framework/react/plugins/persistQueryClient.md
Normal file
@@ -0,0 +1,292 @@
|
||||
---
|
||||
id: persistQueryClient
|
||||
title: persistQueryClient
|
||||
---
|
||||
|
||||
This is set of utilities for interacting with "persisters" which save your queryClient for later use. Different **persisters** can be used to store your client and cache to many different storage layers.
|
||||
|
||||
## Build Persisters
|
||||
|
||||
- [createSyncStoragePersister](./createSyncStoragePersister.md)
|
||||
- [createAsyncStoragePersister](./createAsyncStoragePersister.md)
|
||||
- [create a custom persister](#persisters)
|
||||
|
||||
## How It Works
|
||||
|
||||
**IMPORTANT** - for persist to work properly, you probably want to pass `QueryClient` a `gcTime` value to override the default during hydration (as shown above).
|
||||
|
||||
If it is not set when creating the `QueryClient` instance, it will default to `300000` (5 minutes) for hydration, and the stored cache will be discarded after 5 minutes of inactivity. This is the default garbage collection behavior.
|
||||
|
||||
It should be set as the same value or higher than persistQueryClient's `maxAge` option. E.g. if `maxAge` is 24 hours (the default) then `gcTime` should be 24 hours or higher. If lower than `maxAge`, garbage collection will kick in and discard the stored cache earlier than expected.
|
||||
|
||||
You can also pass it `Infinity` to disable garbage collection behavior entirely.
|
||||
|
||||
Due to a JavaScript limitation, the maximum allowed `gcTime` is about [24 days](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#maximum_delay_value), although it is possible to work around this limit using [timeoutManager.setTimeoutProvider](../reference/interfaces/TimeoutManager.md#settimeoutprovider).
|
||||
|
||||
```tsx
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
gcTime: 1000 * 60 * 60 * 24, // 24 hours
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Cache Busting
|
||||
|
||||
Sometimes you may make changes to your application or data that immediately invalidate any and all cached data. If and when this happens, you can pass a `buster` string option. If the cache that is found does not also have that buster string, it will be discarded. The following several functions accept this option:
|
||||
|
||||
```tsx
|
||||
persistQueryClient({ queryClient, persister, buster: buildHash })
|
||||
persistQueryClientSave({ queryClient, persister, buster: buildHash })
|
||||
persistQueryClientRestore({ queryClient, persister, buster: buildHash })
|
||||
```
|
||||
|
||||
### Removal
|
||||
|
||||
If data is found to be any of the following:
|
||||
|
||||
1. expired (see `maxAge`)
|
||||
2. busted (see `buster`)
|
||||
3. error (ex: `throws ...`)
|
||||
4. empty (ex: `undefined`)
|
||||
|
||||
the persister `removeClient()` is called and the cache is immediately discarded.
|
||||
|
||||
## API
|
||||
|
||||
### `persistQueryClientSave`
|
||||
|
||||
- Your query/mutation are `dehydrated` and stored by the persister you provided.
|
||||
- `createSyncStoragePersister` and `createAsyncStoragePersister` throttle this action to happen at most every 1 second to save on potentially expensive writes. Review their documentation to see how to customize their throttle timing.
|
||||
|
||||
You can use this to explicitly persist the cache at the moment(s) you choose.
|
||||
|
||||
```tsx
|
||||
persistQueryClientSave({
|
||||
queryClient,
|
||||
persister,
|
||||
buster = '',
|
||||
dehydrateOptions = undefined,
|
||||
})
|
||||
```
|
||||
|
||||
### `persistQueryClientSubscribe`
|
||||
|
||||
Runs `persistQueryClientSave` whenever the cache changes for your `queryClient`. For example: you might initiate the `subscribe` when a user logs-in and checks "Remember me".
|
||||
|
||||
- It returns an `unsubscribe` function which you can use to discontinue the monitor; ending the updates to the persisted cache.
|
||||
- If you want to erase the persisted cache after the `unsubscribe`, you can send a new `buster` to `persistQueryClientRestore` which will trigger the persister's `removeClient` function and discard the persisted cache.
|
||||
|
||||
```tsx
|
||||
persistQueryClientSubscribe({
|
||||
queryClient,
|
||||
persister,
|
||||
buster = '',
|
||||
dehydrateOptions = undefined,
|
||||
})
|
||||
```
|
||||
|
||||
### `persistQueryClientRestore`
|
||||
|
||||
- Attempts to `hydrate` a previously persisted dehydrated query/mutation cache from the persister back into the query cache of the passed query client.
|
||||
- If a cache is found that is older than the `maxAge` (which by default is 24 hours), it will be discarded. This timing can be customized as you see fit.
|
||||
|
||||
You can use this to restore the cache at moment(s) you choose.
|
||||
|
||||
```tsx
|
||||
persistQueryClientRestore({
|
||||
queryClient,
|
||||
persister,
|
||||
maxAge = 1000 * 60 * 60 * 24, // 24 hours
|
||||
buster = '',
|
||||
hydrateOptions = undefined,
|
||||
})
|
||||
```
|
||||
|
||||
### `persistQueryClient`
|
||||
|
||||
Takes the following actions:
|
||||
|
||||
1. Immediately restores any persisted cache ([see `persistQueryClientRestore`](#persistqueryclientrestore))
|
||||
2. Subscribes to the query cache and returns the `unsubscribe` function ([see `persistQueryClientSubscribe`](#persistqueryclientsubscribe)).
|
||||
|
||||
This functionality is preserved from version 3.x.
|
||||
|
||||
```tsx
|
||||
persistQueryClient({
|
||||
queryClient,
|
||||
persister,
|
||||
maxAge = 1000 * 60 * 60 * 24, // 24 hours
|
||||
buster = '',
|
||||
hydrateOptions = undefined,
|
||||
dehydrateOptions = undefined,
|
||||
})
|
||||
```
|
||||
|
||||
### `Options`
|
||||
|
||||
All options available are as follows:
|
||||
|
||||
```tsx
|
||||
interface PersistQueryClientOptions {
|
||||
/** The QueryClient to persist */
|
||||
queryClient: QueryClient
|
||||
/** The Persister interface for storing and restoring the cache
|
||||
* to/from a persisted location */
|
||||
persister: Persister
|
||||
/** The max-allowed age of the cache in milliseconds.
|
||||
* If a persisted cache is found that is older than this
|
||||
* time, it will be **silently** discarded
|
||||
* (defaults to 24 hours) */
|
||||
maxAge?: number
|
||||
/** A unique string that can be used to forcefully
|
||||
* invalidate existing caches if they do not share the same buster string */
|
||||
buster?: string
|
||||
/** The options passed to the hydrate function
|
||||
* Not used on `persistQueryClientSave` or `persistQueryClientSubscribe` */
|
||||
hydrateOptions?: HydrateOptions
|
||||
/** The options passed to the dehydrate function
|
||||
* Not used on `persistQueryClientRestore` */
|
||||
dehydrateOptions?: DehydrateOptions
|
||||
}
|
||||
```
|
||||
|
||||
There are actually three interfaces available:
|
||||
|
||||
- `PersistedQueryClientSaveOptions` is used for `persistQueryClientSave` and `persistQueryClientSubscribe` (doesn't use `hydrateOptions`).
|
||||
- `PersistedQueryClientRestoreOptions` is used for `persistQueryClientRestore` (doesn't use `dehydrateOptions`).
|
||||
- `PersistQueryClientOptions` is used for `persistQueryClient`
|
||||
|
||||
## Usage with React
|
||||
|
||||
[persistQueryClient](#persistQueryClient) will try to restore the cache and automatically subscribes to further changes, thus syncing your client to the provided storage.
|
||||
|
||||
However, restoring is asynchronous, because all persisters are async by nature, which means that if you render your App while you are restoring, you might get into race conditions if a query mounts and fetches at the same time.
|
||||
|
||||
Further, if you subscribe to changes outside of the React component lifecycle, you have no way of unsubscribing:
|
||||
|
||||
```tsx
|
||||
// 🚨 never unsubscribes from syncing
|
||||
persistQueryClient({
|
||||
queryClient,
|
||||
persister: localStoragePersister,
|
||||
})
|
||||
|
||||
// 🚨 happens at the same time as restoring
|
||||
ReactDOM.createRoot(rootElement).render(<App />)
|
||||
```
|
||||
|
||||
### PersistQueryClientProvider
|
||||
|
||||
For this use-case, you can use the `PersistQueryClientProvider`. It will make sure to subscribe / unsubscribe correctly according to the React component lifecycle, and it will also make sure that queries will not start fetching while we are still restoring. Queries will still render though, they will just be put into `fetchingState: 'idle'` until data has been restored. Then, they will refetch unless the restored data is _fresh_ enough, and _initialData_ will also be respected. It can be used _instead of_ the normal [QueryClientProvider](../reference/functions/QueryClientProvider.md):
|
||||
|
||||
```tsx
|
||||
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client'
|
||||
import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister'
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
gcTime: 1000 * 60 * 60 * 24, // 24 hours
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const persister = createAsyncStoragePersister({
|
||||
storage: window.localStorage,
|
||||
})
|
||||
|
||||
ReactDOM.createRoot(rootElement).render(
|
||||
<PersistQueryClientProvider
|
||||
client={queryClient}
|
||||
persistOptions={{ persister }}
|
||||
>
|
||||
<App />
|
||||
</PersistQueryClientProvider>,
|
||||
)
|
||||
```
|
||||
|
||||
#### Props
|
||||
|
||||
`PersistQueryClientProvider` takes the same props as [QueryClientProvider](../reference/functions/QueryClientProvider.md), and additionally:
|
||||
|
||||
- `persistOptions: PersistQueryClientOptions`
|
||||
- all [options](#options) you can pass to [persistQueryClient](#persistqueryclient) minus the QueryClient itself
|
||||
- `onSuccess?: () => Promise<unknown> | unknown`
|
||||
- optional
|
||||
- will be called when the initial restore is finished
|
||||
- can be used to [resumePausedMutations](../reference/classes/QueryClient.md#resumepausedmutations)
|
||||
- if a Promise is returned, it will be awaited; restoring is seen as ongoing until then
|
||||
- `onError?: () => Promise<unknown> | unknown`
|
||||
- optional
|
||||
- will be called when an error is thrown during restoration
|
||||
- if a Promise is returned, it will be awaited
|
||||
|
||||
### useIsRestoring
|
||||
|
||||
If you are using the `PersistQueryClientProvider`, you can also use the `useIsRestoring` hook alongside it to check if a restore is currently in progress. `useQuery` and friends also check this internally to avoid race conditions between the restore and mounting queries.
|
||||
|
||||
## Persisters
|
||||
|
||||
### Persisters Interface
|
||||
|
||||
Persisters have the following interfaces:
|
||||
|
||||
```tsx
|
||||
export interface Persister {
|
||||
persistClient(persistClient: PersistedClient): Promisable<void>
|
||||
restoreClient(): Promisable<PersistedClient | undefined>
|
||||
removeClient(): Promisable<void>
|
||||
}
|
||||
```
|
||||
|
||||
Persisted Client entries have the following interface:
|
||||
|
||||
```tsx
|
||||
export interface PersistedClient {
|
||||
timestamp: number
|
||||
buster: string
|
||||
clientState: DehydratedState
|
||||
}
|
||||
```
|
||||
|
||||
You can import these (to build a persister):
|
||||
|
||||
```tsx
|
||||
import {
|
||||
PersistedClient,
|
||||
Persister,
|
||||
} from '@tanstack/react-query-persist-client'
|
||||
```
|
||||
|
||||
### Building A Persister
|
||||
|
||||
You can persist however you like. Here is an example of how to build an [Indexed DB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) persister. Compared to `Web Storage API`, Indexed DB is faster, stores more than 5MB, and doesn't require serialization. That means it can readily store Javascript native types, such as `Date` and `File`.
|
||||
|
||||
```tsx
|
||||
import { get, set, del } from 'idb-keyval'
|
||||
import {
|
||||
PersistedClient,
|
||||
Persister,
|
||||
} from '@tanstack/react-query-persist-client'
|
||||
|
||||
/**
|
||||
* Creates an Indexed DB persister
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API
|
||||
*/
|
||||
export function createIDBPersister(idbValidKey: IDBValidKey = 'reactQuery') {
|
||||
return {
|
||||
persistClient: async (client: PersistedClient) => {
|
||||
await set(idbValidKey, client)
|
||||
},
|
||||
restoreClient: async () => {
|
||||
return await get<PersistedClient>(idbValidKey)
|
||||
},
|
||||
removeClient: async () => {
|
||||
await del(idbValidKey)
|
||||
},
|
||||
} satisfies Persister
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user