0
# Experimental Features
1
2
The package includes experimental functionality for fine-grained, per-query persistence control. These features are marked as experimental and may change in future versions.
3
4
## Capabilities
5
6
### experimental_createQueryPersister Function
7
8
Creates a fine-grained query persister that enables per-query persistence control and storage management.
9
10
```typescript { .api }
11
/**
12
* Warning: experimental feature.
13
* Creates a fine-grained query persister for per-query persistence control
14
* Enables individual queries to be persisted to storage with custom configuration
15
* @param options - Storage and configuration options
16
* @returns Object with persister functions for query-level operations
17
*/
18
function experimental_createQueryPersister<TStorageValue = string>(
19
options: StoragePersisterOptions<TStorageValue>
20
): QueryPersister;
21
22
interface StoragePersisterOptions<TStorageValue = string> {
23
/** The storage client used for setting and retrieving items from cache */
24
storage: AsyncStorage<TStorageValue> | undefined | null;
25
/** How to serialize the data to storage (default: JSON.stringify) */
26
serialize?: (persistedQuery: PersistedQuery) => MaybePromise<TStorageValue>;
27
/** How to deserialize the data from storage (default: JSON.parse) */
28
deserialize?: (cachedString: TStorageValue) => MaybePromise<PersistedQuery>;
29
/** Cache invalidation string for version control */
30
buster?: string;
31
/** Max age in milliseconds (default: 24 hours) */
32
maxAge?: number;
33
/** Storage key prefix (default: 'tanstack-query') */
34
prefix?: string;
35
/** Query filters to narrow down which queries should be persisted */
36
filters?: QueryFilters;
37
}
38
39
interface QueryPersister {
40
/** Custom query function that handles persistence and fetching */
41
persisterFn: <T, TQueryKey extends QueryKey>(
42
queryFn: (context: QueryFunctionContext<TQueryKey>) => T | Promise<T>,
43
ctx: QueryFunctionContext<TQueryKey>,
44
query: Query
45
) => Promise<T>;
46
/** Persist a specific query to storage */
47
persistQuery: (query: Query) => Promise<void>;
48
/** Persist a query by its key */
49
persistQueryByKey: (queryKey: QueryKey, queryClient: QueryClient) => Promise<void>;
50
/** Retrieve a query from storage */
51
retrieveQuery: <T>(
52
queryHash: string,
53
afterRestoreMacroTask?: (persistedQuery: PersistedQuery) => void
54
) => Promise<T | undefined>;
55
/** Garbage collect expired queries from storage */
56
persisterGc: () => Promise<void>;
57
/** Restore multiple queries from storage */
58
restoreQueries: (
59
queryClient: QueryClient,
60
filters?: Pick<QueryFilters, 'queryKey' | 'exact'>
61
) => Promise<void>;
62
}
63
```
64
65
**Usage Example:**
66
67
```typescript
68
import { experimental_createQueryPersister } from '@tanstack/react-query-persist-client';
69
import { useQuery } from '@tanstack/react-query';
70
71
// Create the persister
72
const queryPersister = experimental_createQueryPersister({
73
storage: localStorage,
74
prefix: 'my-app-queries',
75
maxAge: 1000 * 60 * 60 * 2, // 2 hours
76
filters: {
77
// Only persist specific query types
78
queryKey: ['user-data'],
79
},
80
});
81
82
// Use with individual queries
83
function UserProfile({ userId }: { userId: string }) {
84
const { data } = useQuery({
85
queryKey: ['user-data', userId],
86
persister: queryPersister.persisterFn,
87
queryFn: async ({ queryKey }) => {
88
const response = await fetch(`/api/users/${queryKey[1]}`);
89
return response.json();
90
},
91
});
92
93
return <div>{data?.name}</div>;
94
}
95
```
96
97
### AsyncStorage Interface
98
99
Interface for storage backends used by the experimental persister.
100
101
```typescript { .api }
102
interface AsyncStorage<TStorageValue = string> {
103
/** Get an item from storage by key */
104
getItem: (key: string) => MaybePromise<TStorageValue | undefined | null>;
105
/** Set an item in storage */
106
setItem: (key: string, value: TStorageValue) => MaybePromise<unknown>;
107
/** Remove an item from storage */
108
removeItem: (key: string) => MaybePromise<void>;
109
/** Get all entries from storage (optional, needed for garbage collection) */
110
entries?: () => MaybePromise<Array<[key: string, value: TStorageValue]>>;
111
}
112
113
interface PersistedQuery {
114
/** Cache invalidation string */
115
buster: string;
116
/** Hash of the query key */
117
queryHash: string;
118
/** The original query key */
119
queryKey: QueryKey;
120
/** The query state including data and metadata */
121
state: QueryState;
122
}
123
124
type MaybePromise<T> = T | Promise<T>;
125
```
126
127
### Retry Strategies
128
129
Built-in retry strategies for handling persistence failures.
130
131
```typescript { .api }
132
/**
133
* Function type for handling persistence retry scenarios
134
* Called when persistence fails to determine recovery strategy
135
*/
136
type PersistRetryer = (props: {
137
persistedClient: PersistedClient;
138
error: Error;
139
errorCount: number;
140
}) => PersistedClient | undefined;
141
142
/**
143
* Built-in retry strategy that removes the oldest query when persistence fails
144
* Useful for storage quota limitations
145
*/
146
function removeOldestQuery(props: {
147
persistedClient: PersistedClient;
148
error: Error;
149
errorCount: number;
150
}): PersistedClient | undefined;
151
```
152
153
## Advanced Usage Examples
154
155
### Custom Storage Implementation
156
157
```typescript
158
// Create a custom storage that combines localStorage with compression
159
class CompressedStorage implements AsyncStorage<string> {
160
async getItem(key: string) {
161
const compressed = localStorage.getItem(key);
162
return compressed ? LZString.decompress(compressed) : null;
163
}
164
165
async setItem(key: string, value: string) {
166
const compressed = LZString.compress(value);
167
localStorage.setItem(key, compressed);
168
}
169
170
async removeItem(key: string) {
171
localStorage.removeItem(key);
172
}
173
174
async entries() {
175
const entries: Array<[string, string]> = [];
176
for (let i = 0; i < localStorage.length; i++) {
177
const key = localStorage.key(i);
178
if (key) {
179
const value = await this.getItem(key);
180
if (value) entries.push([key, value]);
181
}
182
}
183
return entries;
184
}
185
}
186
187
const persister = experimental_createQueryPersister({
188
storage: new CompressedStorage(),
189
prefix: 'compressed-queries',
190
});
191
```
192
193
### Selective Query Persistence
194
195
```typescript
196
// Only persist queries that match specific criteria
197
const selectivePersister = experimental_createQueryPersister({
198
storage: localStorage,
199
filters: {
200
// Only persist user-specific data
201
predicate: (query) => {
202
const [scope] = query.queryKey as [string, ...any[]];
203
return ['user-profile', 'user-settings', 'user-preferences'].includes(scope);
204
},
205
},
206
// Custom serializer that excludes sensitive data
207
serialize: (persistedQuery) => {
208
const sanitized = {
209
...persistedQuery,
210
state: {
211
...persistedQuery.state,
212
data: sanitizeUserData(persistedQuery.state.data),
213
},
214
};
215
return JSON.stringify(sanitized);
216
},
217
});
218
```
219
220
### Manual Garbage Collection
221
222
```typescript
223
// Set up periodic garbage collection
224
const persister = experimental_createQueryPersister({
225
storage: localStorage,
226
maxAge: 1000 * 60 * 60 * 24, // 24 hours
227
});
228
229
// Run garbage collection every hour
230
setInterval(async () => {
231
try {
232
await persister.persisterGc();
233
console.log('Query cache garbage collection completed');
234
} catch (error) {
235
console.error('Garbage collection failed:', error);
236
}
237
}, 1000 * 60 * 60); // 1 hour
238
```
239
240
### Batch Query Restoration
241
242
```typescript
243
import { QueryClient } from '@tanstack/react-query';
244
245
const queryClient = new QueryClient();
246
const persister = experimental_createQueryPersister({
247
storage: localStorage,
248
});
249
250
// Restore all cached user data on app startup
251
async function restoreUserQueries(userId: string) {
252
await persister.restoreQueries(queryClient, {
253
queryKey: ['user-data', userId],
254
exact: false, // Match all queries starting with this key
255
});
256
}
257
258
// Usage in app initialization
259
async function initializeApp(userId: string) {
260
await restoreUserQueries(userId);
261
// Now user-specific queries are restored from cache
262
}
263
```
264
265
### Custom Query Function with Persistence
266
267
```typescript
268
// Create a custom query function that handles persistence
269
function createPersistedQueryFn<T>(
270
baseFetchFn: () => Promise<T>,
271
persister: ReturnType<typeof experimental_createQueryPersister>
272
) {
273
return async (context: QueryFunctionContext) => {
274
// Let persister handle restoration and caching
275
return persister.persisterFn(
276
() => baseFetchFn(),
277
context,
278
context.query // Query instance from context
279
);
280
};
281
}
282
283
// Usage
284
const fetchUserData = createPersistedQueryFn(
285
() => fetch('/api/user').then(r => r.json()),
286
persister
287
);
288
289
function UserComponent() {
290
const { data } = useQuery({
291
queryKey: ['user'],
292
queryFn: fetchUserData,
293
});
294
295
return <div>{data?.name}</div>;
296
}
297
```
298
299
## Constants
300
301
```typescript { .api }
302
/** Default prefix used for storage keys */
303
const PERSISTER_KEY_PREFIX = 'tanstack-query';
304
```
305
306
Storage keys are formed as `${prefix}-${queryHash}` where prefix defaults to `PERSISTER_KEY_PREFIX`.