0
# Retry Strategies
1
2
Configurable retry mechanisms for handling persistence failures due to storage quotas, size limits, or other constraints. When persistence operations fail, retry strategies can modify the data being persisted to resolve the issue.
3
4
## Capabilities
5
6
### Persist Retryer Function Type
7
8
Defines the signature for retry strategy functions that handle persistence failures.
9
10
```typescript { .api }
11
/**
12
* Function type for retry strategies when persistence fails
13
* @param props - Object containing the failed persistence context
14
* @returns Modified PersistedClient or undefined if retry should give up
15
*/
16
type PersistRetryer = (props: {
17
persistedClient: PersistedClient;
18
error: Error;
19
errorCount: number;
20
}) => PersistedClient | undefined;
21
```
22
23
**Parameters:**
24
- `persistedClient` - The client state that failed to persist
25
- `error` - The error that occurred during persistence
26
- `errorCount` - Number of retry attempts so far
27
28
**Returns:**
29
- `PersistedClient` - Modified client state to retry persistence with
30
- `undefined` - Give up and don't retry
31
32
### Remove Oldest Query Strategy
33
34
Built-in retry strategy that removes the oldest query from the cache to reduce storage size when persistence fails.
35
36
```typescript { .api }
37
/**
38
* Retry strategy that removes the oldest query from cache to reduce size
39
* Sorts queries by dataUpdatedAt and removes the oldest one
40
*/
41
const removeOldestQuery: PersistRetryer;
42
```
43
44
**Usage Example:**
45
46
```typescript
47
import {
48
persistQueryClient,
49
removeOldestQuery,
50
type PersistRetryer,
51
type PersistedClient
52
} from "@tanstack/query-persist-client-core";
53
54
// Simple persister with retry logic
55
function createRetryPersister(baseStorage: Storage, retryStrategy: PersistRetryer) {
56
return {
57
async persistClient(persistedClient: PersistedClient) {
58
let attempts = 0;
59
let currentClient = persistedClient;
60
61
while (attempts < 3) {
62
try {
63
const serialized = JSON.stringify(currentClient);
64
baseStorage.setItem("queryClient", serialized);
65
return; // Success
66
} catch (error) {
67
attempts++;
68
console.warn(`Persistence attempt ${attempts} failed:`, error);
69
70
// Try retry strategy
71
const modifiedClient = retryStrategy({
72
persistedClient: currentClient,
73
error: error as Error,
74
errorCount: attempts
75
});
76
77
if (!modifiedClient) {
78
throw new Error(`Persistence failed after ${attempts} attempts`);
79
}
80
81
currentClient = modifiedClient;
82
}
83
}
84
85
throw new Error("Max retry attempts exceeded");
86
},
87
88
async restoreClient() {
89
const stored = baseStorage.getItem("queryClient");
90
return stored ? JSON.parse(stored) : undefined;
91
},
92
93
async removeClient() {
94
baseStorage.removeItem("queryClient");
95
}
96
};
97
}
98
99
// Use the retry persister with removeOldestQuery strategy
100
const queryClient = new QueryClient();
101
const retryPersister = createRetryPersister(localStorage, removeOldestQuery);
102
103
const [unsubscribe, restorePromise] = persistQueryClient({
104
queryClient,
105
persister: retryPersister,
106
buster: "app-v1.0"
107
});
108
```
109
110
## Custom Retry Strategies
111
112
You can implement custom retry strategies to handle different failure scenarios:
113
114
```typescript
115
// Remove queries by specific criteria
116
const removeStaleQueries: PersistRetryer = ({ persistedClient, error, errorCount }) => {
117
const mutations = [...persistedClient.clientState.mutations];
118
const queries = [...persistedClient.clientState.queries];
119
120
// Remove stale queries (older than 1 hour)
121
const oneHourAgo = Date.now() - (1000 * 60 * 60);
122
const freshQueries = queries.filter(query =>
123
query.state.dataUpdatedAt > oneHourAgo
124
);
125
126
if (freshQueries.length < queries.length) {
127
return {
128
...persistedClient,
129
clientState: { mutations, queries: freshQueries }
130
};
131
}
132
133
return undefined; // No stale queries to remove, give up
134
};
135
136
// Remove mutations to reduce size
137
const removeMutations: PersistRetryer = ({ persistedClient, error, errorCount }) => {
138
if (persistedClient.clientState.mutations.length > 0) {
139
return {
140
...persistedClient,
141
clientState: {
142
mutations: [], // Remove all mutations
143
queries: [...persistedClient.clientState.queries]
144
}
145
};
146
}
147
148
return undefined; // No mutations to remove
149
};
150
151
// Progressive data reduction strategy
152
const progressiveReduction: PersistRetryer = ({ persistedClient, error, errorCount }) => {
153
const mutations = [...persistedClient.clientState.mutations];
154
let queries = [...persistedClient.clientState.queries];
155
156
if (errorCount === 1) {
157
// First attempt: remove mutations
158
return {
159
...persistedClient,
160
clientState: { mutations: [], queries }
161
};
162
} else if (errorCount === 2) {
163
// Second attempt: remove oldest 25% of queries
164
const sortedQueries = queries
165
.sort((a, b) => a.state.dataUpdatedAt - b.state.dataUpdatedAt);
166
const keepCount = Math.ceil(queries.length * 0.75);
167
queries = sortedQueries.slice(-keepCount);
168
169
return {
170
...persistedClient,
171
clientState: { mutations: [], queries }
172
};
173
} else if (errorCount === 3) {
174
// Final attempt: keep only the most recent 5 queries
175
const recentQueries = queries
176
.sort((a, b) => b.state.dataUpdatedAt - a.state.dataUpdatedAt)
177
.slice(0, 5);
178
179
return {
180
...persistedClient,
181
clientState: { mutations: [], queries: recentQueries }
182
};
183
}
184
185
return undefined; // Give up after 3 attempts
186
};
187
```
188
189
## Storage Configuration
190
191
### Storage Options
192
193
```typescript { .api }
194
interface StoragePersisterOptions<TStorageValue = string> {
195
/** The storage client used for setting and retrieving items from cache.
196
* For SSR pass in `undefined`. */
197
storage: AsyncStorage<TStorageValue> | undefined | null;
198
/**
199
* How to serialize the data to storage.
200
* @default `JSON.stringify`
201
*/
202
serialize?: (persistedQuery: PersistedQuery) => MaybePromise<TStorageValue>;
203
/**
204
* How to deserialize the data from storage.
205
* @default `JSON.parse`
206
*/
207
deserialize?: (cachedString: TStorageValue) => MaybePromise<PersistedQuery>;
208
/**
209
* A unique string that can be used to forcefully invalidate existing caches,
210
* if they do not share the same buster string
211
*/
212
buster?: string;
213
/**
214
* The max-allowed age of the cache in milliseconds.
215
* If a persisted cache is found that is older than this
216
* time, it will be discarded
217
* @default 24 hours
218
*/
219
maxAge?: number;
220
/**
221
* Prefix to be used for storage key.
222
* Storage key is a combination of prefix and query hash in a form of `prefix-queryHash`.
223
* @default 'tanstack-query'
224
*/
225
prefix?: string;
226
/**
227
* Filters to narrow down which Queries should be persisted.
228
*/
229
filters?: QueryFilters;
230
}
231
```
232
233
### Persisted Query Data Structure
234
235
```typescript { .api }
236
interface PersistedQuery {
237
buster: string;
238
queryHash: string;
239
queryKey: QueryKey;
240
state: QueryState;
241
}
242
```
243
244
## Constants
245
246
```typescript { .api }
247
/** Default prefix for storage keys used in query persistence */
248
const PERSISTER_KEY_PREFIX = 'tanstack-query';
249
```
250
251
## Error Scenarios
252
253
Retry strategies are designed to handle common persistence failures:
254
255
- **Storage Quota Exceeded**: Most common scenario where storage is full
256
- **Individual Item Too Large**: When a single query's data exceeds storage limits
257
- **Serialization Failures**: When data contains non-serializable values
258
- **Network Errors**: In remote storage scenarios (IndexedDB, remote APIs)
259
- **Permission Errors**: Storage access denied by browser or system
260
261
## Best Practices
262
263
1. **Implement Progressive Reduction**: Start by removing least important data (mutations, stale queries)
264
2. **Set Retry Limits**: Avoid infinite retry loops by limiting attempts
265
3. **Log Failures**: Track which strategies work in your application's context
266
4. **Test Storage Limits**: Understand your target storage backend's limitations
267
5. **Graceful Degradation**: Always return `undefined` when no more reductions are possible