Skip to content

Commit 65ef198

Browse files
fix(experimental_createQueryPersister): return more utilities, rename persister (#8062)
* fix(experimental_createQueryPersister): return more utilities, rename persister * chore: some docs * test: add tests for utilities * ci: apply automated fixes * fix: add missing await --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 08cb4b6 commit 65ef198

File tree

7 files changed

+435
-114
lines changed

7 files changed

+435
-114
lines changed

docs/framework/react/plugins/createPersister.md

+68-15
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
id: createPersister
3-
title: experimental_createPersister
3+
title: experimental_createQueryPersister
44
---
55

66
## Installation
@@ -33,9 +33,9 @@ bun add @tanstack/query-persist-client-core
3333
3434
## Usage
3535

36-
- Import the `experimental_createPersister` function
37-
- Create a new `experimental_createPersister`
38-
- 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.
36+
- Import the `experimental_createQueryPersister` function
37+
- Create a new `experimental_createQueryPersister`
38+
- you can pass any `storage` to it that adheres to the `AsyncStorage` interface - the example below uses the async-storage from React Native.
3939
- 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.
4040
- 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.
4141
- If you provide this `persister` to a single `useQuery` hook, only this Query will be persisted.
@@ -48,16 +48,18 @@ Garbage collecting a Query from memory **does not** affect the persisted data. T
4848
```tsx
4949
import AsyncStorage from '@react-native-async-storage/async-storage'
5050
import { QueryClient } from '@tanstack/react-query'
51-
import { experimental_createPersister } from '@tanstack/query-persist-client-core'
51+
import { experimental_createQueryPersister } from '@tanstack/query-persist-client-core'
52+
53+
const persister = experimental_createQueryPersister({
54+
storage: AsyncStorage,
55+
maxAge: 1000 * 60 * 60 * 12, // 12 hours
56+
})
5257

5358
const queryClient = new QueryClient({
5459
defaultOptions: {
5560
queries: {
5661
gcTime: 1000 * 30, // 30 seconds
57-
persister: experimental_createPersister({
58-
storage: AsyncStorage,
59-
maxAge: 1000 * 60 * 60 * 12, // 12 hours
60-
}),
62+
persister: persister.persisterFn,
6163
},
6264
},
6365
})
@@ -67,12 +69,62 @@ const queryClient = new QueryClient({
6769

6870
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.
6971

72+
## Additional utilities
73+
74+
Invoking `experimental_createQueryPersister` returns additional utilities in addition to `persisterFn` for easier implementation of userland functionalities.
75+
76+
### `persistQueryByKey(queryKey: QueryKey, queryClient: QueryClient): Promise<void>`
77+
78+
This function will persist `Query` to storage and key defined when creating persister.
79+
This utility might be used along `setQueryData` to persist optimistic update to storage without waiting for invalidation.
80+
81+
```tsx
82+
const persister = experimental_createQueryPersister({
83+
storage: AsyncStorage,
84+
maxAge: 1000 * 60 * 60 * 12, // 12 hours
85+
})
86+
87+
const queryClient = useQueryClient()
88+
89+
useMutation({
90+
mutationFn: updateTodo,
91+
onMutate: async (newTodo) => {
92+
...
93+
// Optimistically update to the new value
94+
queryClient.setQueryData(['todos'], (old) => [...old, newTodo])
95+
// And persist it to storage
96+
persister.persistQueryByKey(['todos'], queryClient)
97+
...
98+
},
99+
})
100+
```
101+
102+
### `retrieveQuery<T>(queryHash: string): Promise<T | undefined>`
103+
104+
This function would attempt to retrieve persisted query by `queryHash`.
105+
If `query` is `expired`, `busted` or `malformed` it would be removed from the storage instead, and `undefined` would be returned.
106+
107+
### `persisterGc(): Promise<void>`
108+
109+
This function can be used to sporadically clean up stoage from `expired`, `busted` or `malformed` entries.
110+
111+
For this function to work, your storage must expose `entries` method that would return a `key-value tuple array`.
112+
For example `Object.entries(localStorage)` for `localStorage` or `entries` from `idb-keyval`.
113+
114+
### `persisterRestoreAll(queryClient: QueryClient): Promise<void>`
115+
116+
This function can be used to restore all queries that are currently stored by persister in one go.
117+
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.
118+
119+
For this function to work, your storage must expose `entries` method that would return a `key-value tuple array`.
120+
For example `Object.entries(localStorage)` for `localStorage` or `entries` from `idb-keyval`.
121+
70122
## API
71123

72-
### `experimental_createPersister`
124+
### `experimental_createQueryPersister`
73125

74126
```tsx
75-
experimental_createPersister(options: StoragePersisterOptions)
127+
experimental_createQueryPersister(options: StoragePersisterOptions)
76128
```
77129

78130
#### `Options`
@@ -116,10 +168,11 @@ export interface StoragePersisterOptions {
116168
filters?: QueryFilters
117169
}
118170

119-
interface AsyncStorage {
120-
getItem: (key: string) => Promise<string | undefined | null>
121-
setItem: (key: string, value: string) => Promise<unknown>
122-
removeItem: (key: string) => Promise<void>
171+
interface AsyncStorage<TStorageValue = string> {
172+
getItem: (key: string) => MaybePromise<TStorageValue | undefined | null>
173+
setItem: (key: string, value: TStorageValue) => MaybePromise<unknown>
174+
removeItem: (key: string) => MaybePromise<void>
175+
entries?: () => MaybePromise<Array<[key: string, value: TStorageValue]>>
123176
}
124177
```
125178

docs/framework/vue/plugins/createPersister.md

+12-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
id: createPersister
3-
title: experimental_createPersister
3+
title: experimental_createQueryPersister
44
---
55

66
## Installation
@@ -31,8 +31,8 @@ bun add @tanstack/query-persist-client-core
3131

3232
## Usage
3333

34-
- Import the `experimental_createPersister` function
35-
- Create a new `experimental_createPersister`
34+
- Import the `experimental_createQueryPersister` function
35+
- Create a new `experimental_createQueryPersister`
3636
- you can pass any `storage` to it that adheres to the `AsyncStorage` or `Storage` interface
3737
- 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.
3838
- 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.
@@ -44,16 +44,18 @@ Garbage collecting a Query from memory **does not** affect the persisted data. T
4444

4545
```tsx
4646
import { QueryClient } from '@tanstack/vue-query'
47-
import { experimental_createPersister } from '@tanstack/query-persist-client-core'
47+
import { experimental_createQueryPersister } from '@tanstack/query-persist-client-core'
48+
49+
const persister = experimental_createQueryPersister({
50+
storage: AsyncStorage,
51+
maxAge: 1000 * 60 * 60 * 12, // 12 hours
52+
})
4853

4954
const queryClient = new QueryClient({
5055
defaultOptions: {
5156
queries: {
5257
gcTime: 1000 * 30, // 30 seconds
53-
persister: experimental_createPersister({
54-
storage: localStorage,
55-
maxAge: 1000 * 60 * 60 * 12, // 12 hours
56-
}),
58+
persister: persister.persisterFn,
5759
},
5860
},
5961
})
@@ -65,10 +67,10 @@ The `createPersister` plugin technically wraps the `queryFn`, so it doesn't rest
6567

6668
## API
6769

68-
### `experimental_createPersister`
70+
### `experimental_createQueryPersister`
6971

7072
```tsx
71-
experimental_createPersister(options: StoragePersisterOptions)
73+
experimental_createQueryPersister(options: StoragePersisterOptions)
7274
```
7375

7476
#### `Options`

examples/vue/persister/src/Post.vue

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
<script lang="ts">
2-
import { get, set, del } from 'idb-keyval'
2+
import { get, set, del, entries } from 'idb-keyval'
33
import { defineComponent } from 'vue'
44
import { useQuery } from '@tanstack/vue-query'
55
66
import { Post } from './types'
7-
import { experimental_createPersister } from '@tanstack/query-persist-client-core'
7+
import { experimental_createQueryPersister } from '@tanstack/query-persist-client-core'
88
99
const fetcher = async (id: number): Promise<Post> =>
1010
await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`).then(
@@ -24,13 +24,14 @@ export default defineComponent({
2424
const { isPending, isError, isFetching, data, error } = useQuery({
2525
queryKey: ['post', props.postId] as const,
2626
queryFn: () => fetcher(props.postId),
27-
persister: experimental_createPersister({
27+
persister: experimental_createQueryPersister({
2828
storage: {
2929
getItem: (key: string) => get(key),
3030
setItem: (key: string, value: string) => set(key, value),
3131
removeItem: (key: string) => del(key),
32+
entries: () => entries<string>(),
3233
},
33-
}),
34+
}).persisterFn,
3435
})
3536
3637
return { isPending, isError, isFetching, data, error }

examples/vue/persister/src/Posts.vue

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script lang="ts">
22
import { defineComponent } from 'vue'
33
import { useQuery } from '@tanstack/vue-query'
4-
import { experimental_createPersister } from '@tanstack/query-persist-client-core'
4+
import { experimental_createQueryPersister } from '@tanstack/query-persist-client-core'
55
66
import { Post } from './types'
77
@@ -34,9 +34,9 @@ export default defineComponent({
3434
} = useQuery({
3535
queryKey: ['posts'] as const,
3636
queryFn: () => fetcher(),
37-
persister: experimental_createPersister({
37+
persister: experimental_createQueryPersister({
3838
storage: localStorage,
39-
}),
39+
}).persisterFn,
4040
staleTime: 5000,
4141
})
4242

0 commit comments

Comments
 (0)