0
# Utilities
1
2
Core utility functions for key hashing, data manipulation, query matching, functional programming patterns, and advanced data management operations.
3
4
## Capabilities
5
6
### Key Utilities
7
8
Functions for working with query and mutation keys.
9
10
```typescript { .api }
11
/**
12
* Create a stable hash from a query or mutation key
13
* Used internally for cache key generation and comparison
14
* @param key - Query or mutation key to hash
15
* @returns Stable string hash
16
*/
17
function hashKey(key: QueryKey | MutationKey): string;
18
19
/**
20
* Check if two query keys match partially
21
* Used for flexible query matching in filters
22
* @param a - First query key
23
* @param b - Second query key
24
* @returns true if keys match partially
25
*/
26
function partialMatchKey(a: QueryKey, b: QueryKey): boolean;
27
```
28
29
**Usage Examples:**
30
31
```typescript
32
import { hashKey, partialMatchKey } from "@tanstack/query-core";
33
34
// Hash keys for comparison
35
const hash1 = hashKey(['user', 123]);
36
const hash2 = hashKey(['user', 123]);
37
console.log(hash1 === hash2); // true
38
39
// Partial key matching
40
const matches1 = partialMatchKey(['user'], ['user', 123]); // true
41
const matches2 = partialMatchKey(['user', 123], ['user']); // false
42
const matches3 = partialMatchKey(['user', 123], ['user', 123]); // true
43
44
// Use in custom filtering
45
const findUserQueries = (userId?: number) => {
46
const queries = queryClient.getQueryCache().getAll();
47
return queries.filter(query => {
48
if (userId) {
49
return partialMatchKey(['user', userId], query.queryKey);
50
}
51
return partialMatchKey(['user'], query.queryKey);
52
});
53
};
54
```
55
56
### Query Matching
57
58
Functions for matching queries and mutations against filters.
59
60
```typescript { .api }
61
/**
62
* Check if a query matches the given filters
63
* Used internally by QueryClient methods for filtering operations
64
* @param filters - Query filters to match against
65
* @param query - Query instance to test
66
* @returns true if query matches all filters
67
*/
68
function matchQuery(filters: QueryFilters, query: Query): boolean;
69
70
/**
71
* Check if a mutation matches the given filters
72
* Used internally by QueryClient methods for filtering operations
73
* @param filters - Mutation filters to match against
74
* @param mutation - Mutation instance to test
75
* @returns true if mutation matches all filters
76
*/
77
function matchMutation(filters: MutationFilters, mutation: Mutation): boolean;
78
```
79
80
**Usage Examples:**
81
82
```typescript
83
import { matchQuery, matchMutation } from "@tanstack/query-core";
84
85
const queryCache = queryClient.getQueryCache();
86
const mutationCache = queryClient.getMutationCache();
87
88
// Custom query filtering
89
const customFilter = (query: Query) => {
90
return matchQuery({
91
queryKey: ['user'],
92
stale: true,
93
active: true,
94
}, query);
95
};
96
97
const matchingQueries = queryCache.getAll().filter(customFilter);
98
99
// Custom mutation filtering
100
const pendingUserMutations = mutationCache.getAll().filter(mutation => {
101
return matchMutation({
102
mutationKey: ['user'],
103
status: 'pending',
104
}, mutation);
105
});
106
107
// Advanced filtering logic
108
const complexQueryFilter = (query: Query) => {
109
// Match user queries that are either stale or have errors
110
const userMatch = matchQuery({ queryKey: ['user'] }, query);
111
const staleMatch = matchQuery({ stale: true }, query);
112
const errorMatch = matchQuery({ status: 'error' }, query);
113
114
return userMatch && (staleMatch || errorMatch);
115
};
116
```
117
118
### Data Manipulation
119
120
Functions for data transformation and structural sharing.
121
122
```typescript { .api }
123
/**
124
* Replace data with structural sharing optimization
125
* Preserves object references when data hasn't changed
126
* @param a - Previous data
127
* @param b - New data
128
* @returns Data with optimal structural sharing
129
*/
130
function replaceEqualDeep<T>(a: unknown, b: T): T;
131
132
/**
133
* Special function for keeping previous data during updates
134
* Used with placeholderData to maintain UI state during refetches
135
* @param previousData - The previous data to keep
136
* @returns The same data or undefined
137
*/
138
function keepPreviousData<T>(previousData: T | undefined): T | undefined;
139
```
140
141
**Usage Examples:**
142
143
```typescript
144
import { replaceEqualDeep, keepPreviousData } from "@tanstack/query-core";
145
146
// Structural sharing optimization
147
const oldUserData = { id: 1, name: 'John', settings: { theme: 'dark' } };
148
const newUserData = { id: 1, name: 'John', settings: { theme: 'dark' } };
149
150
const optimizedData = replaceEqualDeep(oldUserData, newUserData);
151
// optimizedData.settings === oldUserData.settings (same reference)
152
153
// Keep previous data during refetch
154
const userQuery = new QueryObserver(queryClient, {
155
queryKey: ['user', userId],
156
queryFn: fetchUser,
157
placeholderData: keepPreviousData,
158
});
159
160
// Custom data transformation with structural sharing
161
const transformData = (oldData, newData) => {
162
const transformed = {
163
...newData,
164
computed: newData.value * 2,
165
timestamp: Date.now(),
166
};
167
168
return replaceEqualDeep(oldData, transformed);
169
};
170
```
171
172
### Special Values and Tokens
173
174
Special values for conditional behavior and skipping operations.
175
176
```typescript { .api }
177
/**
178
* Special token used to skip query execution
179
* When used as queryFn, the query will not execute
180
*/
181
const skipToken: Symbol;
182
183
/**
184
* No-operation function
185
* Useful as a default or placeholder function
186
*/
187
function noop(): void;
188
189
/**
190
* Check if the current environment is server-side
191
* Useful for SSR-aware code
192
*/
193
const isServer: boolean;
194
```
195
196
**Usage Examples:**
197
198
```typescript
199
import { skipToken, noop, isServer } from "@tanstack/query-core";
200
201
// Conditional query execution
202
const userQuery = new QueryObserver(queryClient, {
203
queryKey: ['user', userId],
204
queryFn: userId ? fetchUser : skipToken, // Skip if no userId
205
});
206
207
// Skip query based on conditions
208
const conditionalQuery = new QueryObserver(queryClient, {
209
queryKey: ['data', id],
210
queryFn: shouldFetch ? fetchData : skipToken,
211
});
212
213
// No-op function usage
214
const defaultCallback = noop;
215
216
// Server-side detection
217
if (!isServer) {
218
// Client-side only code
219
setupBrowserEvents();
220
}
221
222
// SSR-aware query setup
223
const browserOnlyQuery = new QueryObserver(queryClient, {
224
queryKey: ['browser-data'],
225
queryFn: isServer ? skipToken : fetchBrowserData,
226
});
227
```
228
229
### Error Handling
230
231
Functions for error handling and decision making.
232
233
```typescript { .api }
234
/**
235
* Determine if an error should be thrown based on configuration
236
* Used internally to decide whether to throw errors or return them in state
237
* @param throwError - Error throwing configuration
238
* @param params - Error and query parameters
239
* @returns true if error should be thrown
240
*/
241
function shouldThrowError<TError, TData>(
242
throwError: ThrowOnError<TData, TError> | undefined,
243
params: [TError, Query<TData, TError>]
244
): boolean;
245
246
/**
247
* Check if a value is a cancelled error
248
* Exported from the retryer module
249
* @param value - Value to check
250
* @returns true if value is a CancelledError
251
*/
252
function isCancelledError(value: any): value is CancelledError;
253
254
/**
255
* Error class for cancelled operations
256
* Thrown when queries or mutations are cancelled
257
* Exported from the retryer module
258
*/
259
class CancelledError extends Error {
260
constructor(options?: { revert?: boolean; silent?: boolean });
261
revert?: boolean;
262
silent?: boolean;
263
}
264
```
265
266
**Usage Examples:**
267
268
```typescript
269
import { shouldThrowError, CancelledError, isCancelledError } from "@tanstack/query-core";
270
271
// Custom error handling logic
272
const customThrowError = (error, query) => {
273
// Only throw errors for specific query types
274
if (query.queryKey[0] === 'critical-data') {
275
return true;
276
}
277
278
// Don't throw network errors
279
if (error.name === 'NetworkError') {
280
return false;
281
}
282
283
return shouldThrowError(true, [error, query]);
284
};
285
286
// Use in query observer
287
const observer = new QueryObserver(queryClient, {
288
queryKey: ['data'],
289
queryFn: fetchData,
290
throwOnError: customThrowError,
291
});
292
293
// Handle cancelled errors
294
try {
295
await queryClient.fetchQuery({
296
queryKey: ['data'],
297
queryFn: fetchData,
298
});
299
} catch (error) {
300
if (isCancelledError(error)) {
301
console.log('Query was cancelled:', error.message);
302
303
if (error.revert) {
304
console.log('Should revert optimistic updates');
305
}
306
307
if (!error.silent) {
308
console.log('Should show error message');
309
}
310
} else {
311
console.error('Query failed:', error);
312
}
313
}
314
315
// Manual cancellation
316
const cancelQuery = () => {
317
throw new CancelledError({
318
revert: true,
319
silent: false
320
});
321
};
322
```
323
324
### Functional Programming Utilities
325
326
Type definitions for functional programming patterns and data updates.
327
328
```typescript { .api }
329
/**
330
* Type for functional updates
331
* Can be either a new value or a function that transforms the old value
332
*/
333
type Updater<T> = T | ((old: T) => T);
334
```
335
336
**Usage Examples:**
337
338
```typescript
339
// The Updater type is exported and used throughout the API
340
type Updater<T> = T | ((old: T) => T);
341
342
// Usage in query data updates
343
queryClient.setQueryData(['user', 123], (oldData) => ({
344
...oldData,
345
lastSeen: new Date().toISOString(),
346
}));
347
348
// Custom functional update utility
349
const updateUserData = (updater: Updater<UserData>) => {
350
const currentData = queryClient.getQueryData(['user', userId]);
351
const newData = typeof updater === 'function'
352
? updater(currentData)
353
: updater;
354
355
queryClient.setQueryData(['user', userId], newData);
356
};
357
358
// Usage
359
updateUserData({ name: 'New Name' }); // Direct value
360
updateUserData(old => ({ ...old, age: old.age + 1 })); // Function update
361
```
362
363
### Development and Debugging
364
365
Utilities for development and debugging scenarios.
366
367
```typescript { .api }
368
// Environment detection
369
if (!isServer) {
370
// Enable development tools
371
if (process.env.NODE_ENV === 'development') {
372
// Development-only code
373
window.__QUERY_CLIENT__ = queryClient;
374
}
375
}
376
377
// Debug helper for query cache
378
const debugQueryCache = () => {
379
const queries = queryClient.getQueryCache().getAll();
380
381
console.table(queries.map(query => ({
382
key: JSON.stringify(query.queryKey),
383
status: query.state.status,
384
fetchStatus: query.state.fetchStatus,
385
dataUpdatedAt: new Date(query.state.dataUpdatedAt).toLocaleString(),
386
observers: query.observers.length,
387
isStale: query.isStale(),
388
isActive: query.isActive(),
389
})));
390
};
391
392
// Debug helper for mutation cache
393
const debugMutationCache = () => {
394
const mutations = queryClient.getMutationCache().getAll();
395
396
console.table(mutations.map(mutation => ({
397
id: mutation.mutationId,
398
key: JSON.stringify(mutation.options.mutationKey),
399
status: mutation.state.status,
400
submittedAt: new Date(mutation.state.submittedAt).toLocaleString(),
401
variables: JSON.stringify(mutation.state.variables),
402
})));
403
};
404
405
// Performance monitoring
406
const monitorQueryPerformance = () => {
407
const cache = queryClient.getQueryCache();
408
409
cache.subscribe((event) => {
410
if (event.type === 'updated' && event.action.type === 'success') {
411
const duration = event.action.dataUpdatedAt - event.action.fetchedAt;
412
console.log(`Query ${JSON.stringify(event.query.queryKey)} took ${duration}ms`);
413
}
414
});
415
};
416
```
417
418
### Advanced Utility Patterns
419
420
Complex utility patterns for advanced use cases.
421
422
```typescript { .api }
423
// Query key factory pattern
424
const queryKeys = {
425
all: ['todos'] as const,
426
lists: () => [...queryKeys.all, 'list'] as const,
427
list: (filters: string) => [...queryKeys.lists(), { filters }] as const,
428
details: () => [...queryKeys.all, 'detail'] as const,
429
detail: (id: number) => [...queryKeys.details(), id] as const,
430
};
431
432
// Usage with utilities
433
const invalidateAllTodos = () => {
434
queryClient.invalidateQueries({ queryKey: queryKeys.all });
435
};
436
437
const invalidateListTodos = () => {
438
queryClient.invalidateQueries({ queryKey: queryKeys.lists() });
439
};
440
441
// Custom matcher utility
442
const createQueryMatcher = (baseKey: QueryKey) => {
443
return (query: Query): boolean => {
444
return partialMatchKey(baseKey, query.queryKey);
445
};
446
};
447
448
const todoMatcher = createQueryMatcher(['todos']);
449
const userMatcher = createQueryMatcher(['user']);
450
451
// Find queries using custom matchers
452
const todoQueries = queryClient.getQueryCache().getAll().filter(todoMatcher);
453
const userQueries = queryClient.getQueryCache().getAll().filter(userMatcher);
454
455
// Batch operations utility
456
const batchQueryOperations = (operations: Array<() => void>) => {
457
notifyManager.batch(() => {
458
operations.forEach(op => op());
459
});
460
};
461
462
// Usage
463
batchQueryOperations([
464
() => queryClient.setQueryData(['user', 1], userData1),
465
() => queryClient.setQueryData(['user', 2], userData2),
466
() => queryClient.invalidateQueries({ queryKey: ['posts'] }),
467
]);
468
```
469
470
## Core Types
471
472
```typescript { .api }
473
type QueryKey = ReadonlyArray<unknown>;
474
type MutationKey = ReadonlyArray<unknown>;
475
476
type Updater<T> = T | ((old: T) => T);
477
478
type ThrowOnError<TData, TError, TQueryData = TData, TQueryKey extends QueryKey = QueryKey> =
479
| boolean
480
| ((error: TError, query: Query<TQueryData, TError, TData, TQueryKey>) => boolean);
481
482
interface QueryFilters {
483
queryKey?: QueryKey;
484
exact?: boolean;
485
stale?: boolean;
486
predicate?: (query: Query) => boolean;
487
fetchStatus?: FetchStatus;
488
status?: QueryStatus;
489
type?: QueryTypeFilter;
490
}
491
492
interface MutationFilters {
493
mutationKey?: MutationKey;
494
exact?: boolean;
495
predicate?: (mutation: Mutation) => boolean;
496
status?: MutationStatus;
497
}
498
499
type QueryTypeFilter = 'all' | 'active' | 'inactive';
500
type FetchStatus = 'fetching' | 'paused' | 'idle';
501
type QueryStatus = 'pending' | 'error' | 'success';
502
type MutationStatus = 'idle' | 'pending' | 'success' | 'error';
503
```