0
# Query Keys and Utilities
1
2
Utilities for working with React Query keys and mutations in the tRPC context. These functions provide access to the underlying query key structure and enable advanced cache manipulation and query utilities creation.
3
4
## Capabilities
5
6
### getQueryKey
7
8
Extract query keys for tRPC procedures to work directly with React Query cache.
9
10
```typescript { .api }
11
/**
12
* Extract the query key for a tRPC procedure
13
* @param procedureOrRouter - tRPC procedure or router
14
* @param ...params - Variable parameters: [input?, type?] for query procedures, [] for routers
15
* @returns tRPC query key for use with React Query
16
*/
17
function getQueryKey<TProcedureOrRouter extends ProcedureOrRouter>(
18
procedureOrRouter: TProcedureOrRouter,
19
...params: GetParams<TProcedureOrRouter>
20
): TRPCQueryKey;
21
22
type GetParams<TProcedureOrRouter extends ProcedureOrRouter> =
23
TProcedureOrRouter extends DecoratedQuery<infer $Def>
24
? [input?: GetQueryProcedureInput<$Def['input']>, type?: QueryType]
25
: [];
26
27
type QueryType = 'any' | 'infinite' | 'query';
28
29
type TRPCQueryKey = [
30
readonly string[],
31
{ input?: unknown; type?: Exclude<QueryType, 'any'> }?
32
];
33
34
type GetQueryProcedureInput<TProcedureInput> = TProcedureInput extends { cursor?: any }
35
? GetInfiniteQueryInput<TProcedureInput>
36
: DeepPartial<TProcedureInput> | undefined;
37
```
38
39
**Usage Examples:**
40
41
```typescript
42
import { getQueryKey } from "@trpc/react-query";
43
import { trpc } from "./utils/trpc";
44
45
function QueryKeyExamples() {
46
const queryClient = useQueryClient();
47
48
const keyExamples = {
49
// Get key for specific query
50
getSpecificUserKey: () => {
51
const key = getQueryKey(trpc.user.get, { id: 1 }, 'query');
52
console.log("User query key:", key);
53
// Result: [['user', 'get'], { input: { id: 1 }, type: 'query' }]
54
},
55
56
// Get key for all queries of a procedure
57
getAllUserKeys: () => {
58
const key = getQueryKey(trpc.user.get);
59
console.log("All user keys:", key);
60
// Result: [['user', 'get']]
61
},
62
63
// Get key for infinite queries
64
getInfinitePostsKey: () => {
65
const key = getQueryKey(trpc.posts.list, { limit: 10 }, 'infinite');
66
console.log("Infinite posts key:", key);
67
// Result: [['posts', 'list'], { input: { limit: 10 }, type: 'infinite' }]
68
},
69
70
// Use keys with React Query directly
71
invalidateWithKey: () => {
72
const userKey = getQueryKey(trpc.user.get, { id: 1 });
73
queryClient.invalidateQueries({ queryKey: userKey });
74
},
75
76
// Check if data exists in cache
77
hasDataInCache: (userId: number) => {
78
const key = getQueryKey(trpc.user.get, { id: userId });
79
const data = queryClient.getQueryData(key);
80
return data !== undefined;
81
},
82
};
83
84
return (
85
<div>
86
<button onClick={keyExamples.getSpecificUserKey}>
87
Get Specific User Key
88
</button>
89
<button onClick={keyExamples.invalidateWithKey}>
90
Invalidate with Key
91
</button>
92
</div>
93
);
94
}
95
```
96
97
### getMutationKey
98
99
Extract mutation keys for tRPC mutation procedures.
100
101
```typescript { .api }
102
/**
103
* Extract the mutation key for a tRPC mutation procedure
104
* @param procedure - tRPC mutation procedure
105
* @returns tRPC mutation key for use with React Query
106
*/
107
function getMutationKey<TProcedure extends DecoratedMutation<any>>(
108
procedure: TProcedure
109
): TRPCMutationKey;
110
111
type TRPCMutationKey = [readonly string[]];
112
```
113
114
**Usage Examples:**
115
116
```typescript
117
import { getMutationKey } from "@trpc/react-query";
118
import { trpc } from "./utils/trpc";
119
120
function MutationKeyExamples() {
121
const queryClient = useQueryClient();
122
123
const mutationKeyExamples = {
124
// Get mutation key
125
getUserMutationKey: () => {
126
const key = getMutationKey(trpc.user.create);
127
console.log("User create mutation key:", key);
128
// Result: [['user', 'create']]
129
},
130
131
// Check if mutation is in progress
132
isMutationInProgress: () => {
133
const key = getMutationKey(trpc.user.create);
134
const mutation = queryClient.getMutationCache().find({ mutationKey: key });
135
return mutation?.state.status === 'pending';
136
},
137
138
// Cancel specific mutations
139
cancelMutation: () => {
140
const key = getMutationKey(trpc.user.create);
141
queryClient.getMutationCache().findAll({ mutationKey: key })
142
.forEach(mutation => mutation.destroy());
143
},
144
145
// Get mutation count
146
getMutationCount: () => {
147
const key = getMutationKey(trpc.user.create);
148
return queryClient.getMutationCache().findAll({ mutationKey: key }).length;
149
},
150
};
151
152
return (
153
<div>
154
<button onClick={mutationKeyExamples.getUserMutationKey}>
155
Get Mutation Key
156
</button>
157
<button onClick={mutationKeyExamples.cancelMutation}>
158
Cancel Mutations
159
</button>
160
</div>
161
);
162
}
163
```
164
165
### createTRPCQueryUtils
166
167
Create standalone query utilities without React hooks.
168
169
```typescript { .api }
170
/**
171
* Creates query utilities for imperative tRPC operations outside React components
172
* @param opts - Configuration with tRPC client and React Query client
173
* @returns Utility functions mirroring your router structure
174
*/
175
function createTRPCQueryUtils<TRouter extends AnyRouter>(
176
opts: CreateQueryUtilsOptions<TRouter>
177
): TRPCQueryUtils<TRouter>;
178
179
interface CreateQueryUtilsOptions<TRouter extends AnyRouter> {
180
/** tRPC client instance */
181
client: TRPCClient<TRouter> | TRPCUntypedClient<TRouter>;
182
/** React Query client instance */
183
queryClient: QueryClient;
184
}
185
186
interface TRPCQueryUtils<TRouter> {
187
// Utility methods are generated based on your router structure
188
// Each procedure gets utility methods for cache manipulation
189
}
190
```
191
192
**Usage Examples:**
193
194
```typescript
195
import { createTRPCQueryUtils } from "@trpc/react-query";
196
import { QueryClient } from "@tanstack/react-query";
197
198
// Create utils outside React components
199
const queryClient = new QueryClient();
200
const trpcClient = createTRPCClient({
201
url: "http://localhost:3000/api/trpc",
202
});
203
204
const utils = createTRPCQueryUtils({
205
client: trpcClient,
206
queryClient,
207
});
208
209
// Use in non-React contexts
210
async function backgroundSync() {
211
// Fetch and cache data
212
await utils.user.list.fetch();
213
214
// Invalidate stale data
215
await utils.user.invalidate();
216
217
// Prefetch upcoming data
218
await utils.posts.trending.prefetch();
219
}
220
221
// Service worker usage
222
self.addEventListener('sync', async (event) => {
223
if (event.tag === 'background-sync') {
224
await backgroundSync();
225
}
226
});
227
228
// Node.js script usage
229
async function dataPreprocessing() {
230
const users = await utils.user.list.fetch();
231
232
for (const user of users) {
233
// Process each user
234
await utils.user.analytics.prefetch({ userId: user.id });
235
}
236
237
console.log("Data preprocessing complete");
238
}
239
```
240
241
### Query Key Structure
242
243
Understanding the tRPC query key structure for advanced cache manipulation.
244
245
```typescript { .api }
246
// Query key structure
247
type TRPCQueryKey = [
248
readonly string[], // Procedure path: ['user', 'get']
249
{
250
input?: unknown; // Procedure input
251
type?: 'query' | 'infinite'; // Query type
252
}?
253
];
254
255
// Examples of query keys:
256
// trpc.user.get.useQuery({ id: 1 })
257
// Key: [['user', 'get'], { input: { id: 1 }, type: 'query' }]
258
259
// trpc.posts.list.useInfiniteQuery({ limit: 10 })
260
// Key: [['posts', 'list'], { input: { limit: 10 }, type: 'infinite' }]
261
262
// trpc.user.get.invalidate() (all user.get queries)
263
// Key: [['user', 'get']]
264
265
// trpc.invalidate() (all queries)
266
// Key: []
267
```
268
269
**Usage Examples:**
270
271
```typescript
272
function AdvancedCacheManipulation() {
273
const queryClient = useQueryClient();
274
275
const cacheExamples = {
276
// Find all user queries
277
findAllUserQueries: () => {
278
const queries = queryClient.getQueryCache().findAll({
279
queryKey: [['user']],
280
type: 'active',
281
});
282
return queries;
283
},
284
285
// Find specific user data queries
286
findUserDataQueries: (userId: number) => {
287
const queries = queryClient.getQueryCache().findAll({
288
queryKey: [['user', 'get']],
289
predicate: (query) => {
290
const [, params] = query.queryKey as TRPCQueryKey;
291
return params?.input?.id === userId;
292
},
293
});
294
return queries;
295
},
296
297
// Find all infinite queries
298
findInfiniteQueries: () => {
299
const queries = queryClient.getQueryCache().findAll({
300
predicate: (query) => {
301
const [, params] = query.queryKey as TRPCQueryKey;
302
return params?.type === 'infinite';
303
},
304
});
305
return queries;
306
},
307
308
// Custom cache invalidation
309
invalidateUserRelatedQueries: (userId: number) => {
310
queryClient.invalidateQueries({
311
predicate: (query) => {
312
const [path, params] = query.queryKey as TRPCQueryKey;
313
314
// Invalidate user-specific queries
315
if (path[0] === 'user' && params?.input?.id === userId) {
316
return true;
317
}
318
319
// Invalidate user's posts
320
if (path.join('.') === 'posts.byUser' && params?.input?.userId === userId) {
321
return true;
322
}
323
324
return false;
325
},
326
});
327
},
328
};
329
330
return (
331
<div>
332
<button onClick={() => cacheExamples.invalidateUserRelatedQueries(1)}>
333
Invalidate User 1 Related Queries
334
</button>
335
</div>
336
);
337
}
338
```
339
340
### Advanced Query Filtering
341
342
Use query keys for sophisticated cache filtering and manipulation.
343
344
```typescript
345
function QueryFiltering() {
346
const queryClient = useQueryClient();
347
348
const filteringExamples = {
349
// Remove stale user queries
350
removeStaleUserQueries: () => {
351
const staleTime = 5 * 60 * 1000; // 5 minutes
352
const now = Date.now();
353
354
queryClient.getQueryCache().findAll({
355
queryKey: [['user']],
356
predicate: (query) => {
357
const dataUpdatedAt = query.state.dataUpdatedAt;
358
return now - dataUpdatedAt > staleTime;
359
},
360
}).forEach(query => {
361
queryClient.removeQueries({ queryKey: query.queryKey });
362
});
363
},
364
365
// Get query statistics
366
getQueryStatistics: () => {
367
const allQueries = queryClient.getQueryCache().getAll();
368
const trpcQueries = allQueries.filter(query =>
369
Array.isArray(query.queryKey[0])
370
);
371
372
const stats = {
373
total: trpcQueries.length,
374
success: trpcQueries.filter(q => q.state.status === 'success').length,
375
error: trpcQueries.filter(q => q.state.status === 'error').length,
376
loading: trpcQueries.filter(q => q.state.status === 'pending').length,
377
};
378
379
return stats;
380
},
381
382
// Find queries by input pattern
383
findQueriesByInputPattern: (pattern: any) => {
384
return queryClient.getQueryCache().findAll({
385
predicate: (query) => {
386
const [, params] = query.queryKey as TRPCQueryKey;
387
return JSON.stringify(params?.input).includes(JSON.stringify(pattern));
388
},
389
});
390
},
391
};
392
393
const stats = filteringExamples.getQueryStatistics();
394
395
return (
396
<div>
397
<p>Total tRPC queries: {stats.total}</p>
398
<p>Success: {stats.success}</p>
399
<p>Error: {stats.error}</p>
400
<p>Loading: {stats.loading}</p>
401
402
<button onClick={filteringExamples.removeStaleUserQueries}>
403
Remove Stale User Queries
404
</button>
405
</div>
406
);
407
}
408
```
409
410
## Common Patterns
411
412
### Cache Debugging
413
414
```typescript
415
function CacheDebugging() {
416
const queryClient = useQueryClient();
417
418
const debugCache = () => {
419
const cache = queryClient.getQueryCache().getAll();
420
421
console.group("tRPC Query Cache Debug");
422
cache.forEach((query) => {
423
if (Array.isArray(query.queryKey[0])) {
424
const [path, params] = query.queryKey as TRPCQueryKey;
425
console.log({
426
procedure: path.join('.'),
427
input: params?.input,
428
type: params?.type,
429
status: query.state.status,
430
dataUpdatedAt: new Date(query.state.dataUpdatedAt),
431
data: query.state.data,
432
});
433
}
434
});
435
console.groupEnd();
436
};
437
438
return (
439
<button onClick={debugCache}>
440
Debug Cache
441
</button>
442
);
443
}
444
```
445
446
### Query Key Utilities
447
448
```typescript
449
function QueryKeyUtilities() {
450
// Helper functions for working with tRPC query keys
451
const keyUtils = {
452
// Check if a key matches a procedure
453
matchesProcedure: (key: TRPCQueryKey, procedurePath: string[]) => {
454
const [path] = key;
455
return path.length >= procedurePath.length &&
456
procedurePath.every((segment, index) => path[index] === segment);
457
},
458
459
// Extract procedure path from key
460
getProcedurePath: (key: TRPCQueryKey) => {
461
const [path] = key;
462
return path.join('.');
463
},
464
465
// Check if key has specific input
466
hasInput: (key: TRPCQueryKey, input: any) => {
467
const [, params] = key;
468
return JSON.stringify(params?.input) === JSON.stringify(input);
469
},
470
471
// Get query type from key
472
getQueryType: (key: TRPCQueryKey) => {
473
const [, params] = key;
474
return params?.type || 'query';
475
},
476
};
477
478
return keyUtils;
479
}
480
```
481
482
### Batch Operations
483
484
```typescript
485
function BatchOperations() {
486
const queryClient = useQueryClient();
487
488
const batchOps = {
489
// Batch invalidate multiple procedures
490
batchInvalidate: async (procedures: Array<{ path: string[], input?: any }>) => {
491
await Promise.all(
492
procedures.map(({ path, input }) => {
493
const key = input
494
? [path, { input }] as TRPCQueryKey
495
: [path] as TRPCQueryKey;
496
return queryClient.invalidateQueries({ queryKey: key });
497
})
498
);
499
},
500
501
// Batch prefetch related data
502
batchPrefetch: async (userId: number) => {
503
const utils = createTRPCQueryUtils({
504
client: trpcClient,
505
queryClient,
506
});
507
508
await Promise.all([
509
utils.user.get.prefetch({ id: userId }),
510
utils.user.posts.prefetch({ userId }),
511
utils.user.followers.prefetch({ userId }),
512
utils.user.settings.prefetch({ userId }),
513
]);
514
},
515
};
516
517
return (
518
<button onClick={() => batchOps.batchPrefetch(1)}>
519
Batch Prefetch User Data
520
</button>
521
);
522
}
523
```