Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 68 additions & 15 deletions docs/framework/react/plugins/createPersister.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
id: createPersister
title: experimental_createPersister
title: experimental_createQueryPersister
---

## Installation
Expand Down Expand Up @@ -33,9 +33,9 @@ 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 - the example below uses the async-storage from React Native.
- 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.
Expand All @@ -48,16 +48,18 @@ Garbage collecting a Query from memory **does not** affect the persisted data. T
```tsx
import AsyncStorage from '@react-native-async-storage/async-storage'
import { QueryClient } from '@tanstack/react-query'
import { experimental_createPersister } from '@tanstack/query-persist-client-core'
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: experimental_createPersister({
storage: AsyncStorage,
maxAge: 1000 * 60 * 60 * 12, // 12 hours
}),
persister: persister.persisterFn,
},
},
})
Expand All @@ -67,12 +69,62 @@ const queryClient = new QueryClient({

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 stoage 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`.

### `persisterRestoreAll(queryClient: QueryClient): Promise<void>`

This function can be used to restore all queries that are currently stored by persister in one go.
For example when your app is starting up in offline mode, or you want data from previous session to be immediately available without intermediate `loading` state.

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_createPersister`
### `experimental_createQueryPersister`

```tsx
experimental_createPersister(options: StoragePersisterOptions)
experimental_createQueryPersister(options: StoragePersisterOptions)
```

#### `Options`
Expand Down Expand Up @@ -116,10 +168,11 @@ export interface StoragePersisterOptions {
filters?: QueryFilters
}

interface AsyncStorage {
getItem: (key: string) => Promise<string | undefined | null>
setItem: (key: string, value: string) => Promise<unknown>
removeItem: (key: string) => Promise<void>
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]>>
}
```

Expand Down
22 changes: 12 additions & 10 deletions docs/framework/vue/plugins/createPersister.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
id: createPersister
title: experimental_createPersister
title: experimental_createQueryPersister
---

## Installation
Expand Down Expand Up @@ -31,8 +31,8 @@ bun add @tanstack/query-persist-client-core

## Usage

- Import the `experimental_createPersister` function
- Create a new `experimental_createPersister`
- Import the `experimental_createQueryPersister` function
- Create a new `experimental_createQueryPersister`
- 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.
Expand All @@ -44,16 +44,18 @@ Garbage collecting a Query from memory **does not** affect the persisted data. T

```tsx
import { QueryClient } from '@tanstack/vue-query'
import { experimental_createPersister } from '@tanstack/query-persist-client-core'
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: experimental_createPersister({
storage: localStorage,
maxAge: 1000 * 60 * 60 * 12, // 12 hours
}),
persister: persister.persisterFn,
},
},
})
Expand All @@ -65,10 +67,10 @@ The `createPersister` plugin technically wraps the `queryFn`, so it doesn't rest

## API

### `experimental_createPersister`
### `experimental_createQueryPersister`

```tsx
experimental_createPersister(options: StoragePersisterOptions)
experimental_createQueryPersister(options: StoragePersisterOptions)
```

#### `Options`
Expand Down
9 changes: 5 additions & 4 deletions examples/vue/persister/src/Post.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<script lang="ts">
import { get, set, del } from 'idb-keyval'
import { get, set, del, entries } from 'idb-keyval'
import { defineComponent } from 'vue'
import { useQuery } from '@tanstack/vue-query'

import { Post } from './types'
import { experimental_createPersister } from '@tanstack/query-persist-client-core'
import { experimental_createQueryPersister } from '@tanstack/query-persist-client-core'

const fetcher = async (id: number): Promise<Post> =>
await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`).then(
Expand All @@ -24,13 +24,14 @@ export default defineComponent({
const { isPending, isError, isFetching, data, error } = useQuery({
queryKey: ['post', props.postId] as const,
queryFn: () => fetcher(props.postId),
persister: experimental_createPersister({
persister: experimental_createQueryPersister({
storage: {
getItem: (key: string) => get(key),
setItem: (key: string, value: string) => set(key, value),
removeItem: (key: string) => del(key),
entries: () => entries<string>(),
},
}),
}).persisterFn,
})

return { isPending, isError, isFetching, data, error }
Expand Down
6 changes: 3 additions & 3 deletions examples/vue/persister/src/Posts.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script lang="ts">
import { defineComponent } from 'vue'
import { useQuery } from '@tanstack/vue-query'
import { experimental_createPersister } from '@tanstack/query-persist-client-core'
import { experimental_createQueryPersister } from '@tanstack/query-persist-client-core'

import { Post } from './types'

Expand Down Expand Up @@ -34,9 +34,9 @@ export default defineComponent({
} = useQuery({
queryKey: ['posts'] as const,
queryFn: () => fetcher(),
persister: experimental_createPersister({
persister: experimental_createQueryPersister({
storage: localStorage,
}),
}).persisterFn,
staleTime: 5000,
})

Expand Down
Loading