0
# Parallel Queries
1
2
Execute multiple queries in parallel with type-safe results and coordinated loading states. The `useQueries` hook allows you to run multiple queries simultaneously while maintaining individual query states.
3
4
## Capabilities
5
6
### useQueries Hook
7
8
The main hook for executing multiple queries in parallel with full type safety.
9
10
```typescript { .api }
11
/**
12
* Execute multiple queries in parallel with type-safe results
13
* @param config - Configuration object with queries array and optional context
14
* @returns Array of query results matching input query types
15
*/
16
function useQueries<T extends any[]>({
17
queries,
18
context,
19
}: {
20
queries: readonly [...QueriesOptions<T>];
21
context?: React.Context<QueryClient | undefined>;
22
}): QueriesResults<T>;
23
```
24
25
**Usage Examples:**
26
27
```typescript
28
import { useQueries } from "react-query";
29
30
// Basic parallel queries
31
const results = useQueries({
32
queries: [
33
{
34
queryKey: ['user', userId],
35
queryFn: () => fetchUser(userId),
36
enabled: !!userId
37
},
38
{
39
queryKey: ['posts', userId],
40
queryFn: () => fetchUserPosts(userId),
41
enabled: !!userId
42
},
43
{
44
queryKey: ['settings'],
45
queryFn: fetchSettings
46
}
47
]
48
});
49
50
const [userQuery, postsQuery, settingsQuery] = results;
51
52
// Type-safe access to individual results
53
const user = userQuery.data;
54
const posts = postsQuery.data;
55
const settings = settingsQuery.data;
56
57
// Check loading states
58
const isLoadingAny = results.some(result => result.isLoading);
59
const isLoadingAll = results.every(result => result.isLoading);
60
61
// Dynamic parallel queries
62
function UserDashboard({ userIds }: { userIds: string[] }) {
63
const userQueries = useQueries({
64
queries: userIds.map(id => ({
65
queryKey: ['user', id],
66
queryFn: () => fetchUser(id),
67
staleTime: 5 * 60 * 1000 // 5 minutes
68
}))
69
});
70
71
const users = userQueries.map(query => query.data).filter(Boolean);
72
const isLoading = userQueries.some(query => query.isLoading);
73
const hasError = userQueries.some(query => query.isError);
74
75
if (isLoading) return <div>Loading users...</div>;
76
if (hasError) return <div>Error loading some users</div>;
77
78
return (
79
<div>
80
{users.map(user => (
81
<UserCard key={user.id} user={user} />
82
))}
83
</div>
84
);
85
}
86
```
87
88
### Type-Safe Query Configuration
89
90
Advanced type inference for query options and results.
91
92
```typescript { .api }
93
/**
94
* Complex type system for inferring query options and results
95
* Supports explicit type parameters and automatic inference
96
*/
97
type QueriesOptions<
98
T extends any[],
99
Result extends any[] = [],
100
Depth extends ReadonlyArray<number> = []
101
> = Depth['length'] extends 20 // Maximum depth limit
102
? UseQueryOptions[]
103
: T extends []
104
? []
105
: T extends [infer Head]
106
? [...Result, GetOptions<Head>]
107
: T extends [infer Head, ...infer Tail]
108
? QueriesOptions<[...Tail], [...Result, GetOptions<Head>], [...Depth, 1]>
109
: T extends UseQueryOptions<infer TQueryFnData, infer TError, infer TData, infer TQueryKey>[]
110
? UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>[]
111
: UseQueryOptions[];
112
113
type QueriesResults<
114
T extends any[],
115
Result extends any[] = [],
116
Depth extends ReadonlyArray<number> = []
117
> = Depth['length'] extends 20 // Maximum depth limit
118
? UseQueryResult[]
119
: T extends []
120
? []
121
: T extends [infer Head]
122
? [...Result, GetResults<Head>]
123
: T extends [infer Head, ...infer Tail]
124
? QueriesResults<[...Tail], [...Result, GetResults<Head>], [...Depth, 1]>
125
: T extends UseQueryOptions<infer TQueryFnData, infer TError, infer TData, any>[]
126
? UseQueryResult<unknown extends TData ? TQueryFnData : TData, TError>[]
127
: UseQueryResult[];
128
```
129
130
### Individual Query Options
131
132
Each query in the array uses standard `UseQueryOptions` without the `context` property.
133
134
```typescript { .api }
135
type UseQueryOptionsForUseQueries<
136
TQueryFnData = unknown,
137
TError = unknown,
138
TData = TQueryFnData,
139
TQueryKey extends QueryKey = QueryKey
140
> = Omit<UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>, 'context'>;
141
```
142
143
## Advanced Usage Patterns
144
145
### Conditional Parallel Queries
146
147
Running queries conditionally while maintaining type safety:
148
149
```typescript
150
function ConditionalDashboard({ userId, isAdmin }: { userId: string; isAdmin: boolean }) {
151
const queries = useQueries({
152
queries: [
153
// Always fetch user data
154
{
155
queryKey: ['user', userId],
156
queryFn: () => fetchUser(userId)
157
},
158
// Only fetch admin data if user is admin
159
{
160
queryKey: ['admin-stats'],
161
queryFn: fetchAdminStats,
162
enabled: isAdmin
163
},
164
// Conditional query based on user data
165
{
166
queryKey: ['user-preferences', userId],
167
queryFn: () => fetchUserPreferences(userId),
168
enabled: !!userId
169
}
170
]
171
});
172
173
const [userQuery, adminStatsQuery, preferencesQuery] = queries;
174
175
// Handle different loading states
176
const criticalDataLoading = userQuery.isLoading;
177
const optionalDataLoading = adminStatsQuery.isLoading || preferencesQuery.isLoading;
178
179
return (
180
<div>
181
{criticalDataLoading ? (
182
<div>Loading user data...</div>
183
) : (
184
<div>
185
<h1>{userQuery.data?.name}</h1>
186
187
{isAdmin && adminStatsQuery.data && (
188
<AdminPanel stats={adminStatsQuery.data} />
189
)}
190
191
{preferencesQuery.data && (
192
<PreferencesPanel preferences={preferencesQuery.data} />
193
)}
194
</div>
195
)}
196
</div>
197
);
198
}
199
```
200
201
### Dynamic Query Generation
202
203
Generating queries based on runtime data:
204
205
```typescript
206
interface DashboardData {
207
userId: string;
208
widgets: Array<{
209
id: string;
210
type: 'analytics' | 'notifications' | 'activity';
211
config: Record<string, any>;
212
}>;
213
}
214
215
function DynamicDashboard({ dashboardData }: { dashboardData: DashboardData }) {
216
const queries = useQueries({
217
queries: [
218
// Base user query
219
{
220
queryKey: ['user', dashboardData.userId],
221
queryFn: () => fetchUser(dashboardData.userId)
222
},
223
// Dynamic widget queries
224
...dashboardData.widgets.map(widget => ({
225
queryKey: ['widget', widget.id, widget.type],
226
queryFn: () => fetchWidgetData(widget.type, widget.config),
227
staleTime: widget.type === 'analytics' ? 5 * 60 * 1000 : 30 * 1000
228
}))
229
]
230
});
231
232
const [userQuery, ...widgetQueries] = queries;
233
234
const isLoadingCritical = userQuery.isLoading;
235
const isLoadingWidgets = widgetQueries.some(q => q.isLoading);
236
237
return (
238
<div>
239
{isLoadingCritical ? (
240
<div>Loading dashboard...</div>
241
) : (
242
<div>
243
<UserHeader user={userQuery.data} />
244
245
<div className="widgets-grid">
246
{dashboardData.widgets.map((widget, index) => {
247
const widgetQuery = widgetQueries[index];
248
249
return (
250
<Widget
251
key={widget.id}
252
type={widget.type}
253
data={widgetQuery.data}
254
isLoading={widgetQuery.isLoading}
255
error={widgetQuery.error}
256
/>
257
);
258
})}
259
</div>
260
</div>
261
)}
262
</div>
263
);
264
}
265
```
266
267
### Coordinated Error Handling
268
269
Handling errors across multiple parallel queries:
270
271
```typescript
272
function RobustParallelQueries() {
273
const queries = useQueries({
274
queries: [
275
{
276
queryKey: ['critical-data'],
277
queryFn: fetchCriticalData,
278
retry: 3
279
},
280
{
281
queryKey: ['optional-data'],
282
queryFn: fetchOptionalData,
283
retry: 1,
284
onError: () => {
285
// Log optional data errors but don't show to user
286
console.warn('Optional data failed to load');
287
}
288
},
289
{
290
queryKey: ['fallback-data'],
291
queryFn: fetchFallbackData,
292
retry: false
293
}
294
]
295
});
296
297
const [criticalQuery, optionalQuery, fallbackQuery] = queries;
298
299
// Critical error handling
300
if (criticalQuery.isError) {
301
return (
302
<ErrorState
303
error={criticalQuery.error}
304
onRetry={() => criticalQuery.refetch()}
305
/>
306
);
307
}
308
309
// Show loading state while critical data loads
310
if (criticalQuery.isLoading) {
311
return <LoadingState />;
312
}
313
314
return (
315
<div>
316
<CriticalDataComponent data={criticalQuery.data} />
317
318
{optionalQuery.data && (
319
<OptionalDataComponent data={optionalQuery.data} />
320
)}
321
322
{optionalQuery.isError && (
323
<div className="warning">
324
Some features may be limited due to data loading issues.
325
</div>
326
)}
327
328
{fallbackQuery.data && (
329
<FallbackDataComponent data={fallbackQuery.data} />
330
)}
331
</div>
332
);
333
}
334
```
335
336
### Performance Monitoring
337
338
Tracking performance metrics across parallel queries:
339
340
```typescript
341
function MonitoredParallelQueries() {
342
const startTime = useRef(Date.now());
343
const [metrics, setMetrics] = useState({
344
totalQueries: 0,
345
completedQueries: 0,
346
failedQueries: 0,
347
averageLoadTime: 0
348
});
349
350
const queries = useQueries({
351
queries: [
352
{
353
queryKey: ['user-data'],
354
queryFn: () => fetchUserData(),
355
onSuccess: () => trackQuerySuccess('user-data'),
356
onError: () => trackQueryError('user-data')
357
},
358
{
359
queryKey: ['analytics'],
360
queryFn: () => fetchAnalytics(),
361
onSuccess: () => trackQuerySuccess('analytics'),
362
onError: () => trackQueryError('analytics')
363
},
364
{
365
queryKey: ['notifications'],
366
queryFn: () => fetchNotifications(),
367
onSuccess: () => trackQuerySuccess('notifications'),
368
onError: () => trackQueryError('notifications')
369
}
370
]
371
});
372
373
const trackQuerySuccess = (queryName: string) => {
374
const loadTime = Date.now() - startTime.current;
375
console.log(`${queryName} loaded in ${loadTime}ms`);
376
377
setMetrics(prev => ({
378
...prev,
379
completedQueries: prev.completedQueries + 1,
380
averageLoadTime: (prev.averageLoadTime + loadTime) / 2
381
}));
382
};
383
384
const trackQueryError = (queryName: string) => {
385
console.error(`${queryName} failed to load`);
386
setMetrics(prev => ({
387
...prev,
388
failedQueries: prev.failedQueries + 1
389
}));
390
};
391
392
const allSettled = queries.every(q => !q.isLoading);
393
const hasErrors = queries.some(q => q.isError);
394
395
useEffect(() => {
396
if (allSettled) {
397
const totalTime = Date.now() - startTime.current;
398
console.log(`All queries settled in ${totalTime}ms`);
399
400
// Send analytics
401
analytics.track('parallel_queries_completed', {
402
totalTime,
403
queryCount: queries.length,
404
errorCount: metrics.failedQueries,
405
successCount: metrics.completedQueries
406
});
407
}
408
}, [allSettled]);
409
410
return (
411
<div>
412
{/* Development metrics display */}
413
{process.env.NODE_ENV === 'development' && (
414
<div className="debug-metrics">
415
<p>Queries: {queries.length}</p>
416
<p>Completed: {metrics.completedQueries}</p>
417
<p>Failed: {metrics.failedQueries}</p>
418
<p>Avg Load Time: {metrics.averageLoadTime.toFixed(0)}ms</p>
419
</div>
420
)}
421
422
{/* Main content */}
423
{queries.map((query, index) => (
424
<QueryResult key={index} query={query} />
425
))}
426
</div>
427
);
428
}
429
```
430
431
### Type-Safe Query Factories
432
433
Creating reusable query configurations:
434
435
```typescript
436
// Query factory functions
437
const createUserQuery = (userId: string) => ({
438
queryKey: ['user', userId] as const,
439
queryFn: () => fetchUser(userId),
440
enabled: !!userId
441
});
442
443
const createPostsQuery = (userId: string) => ({
444
queryKey: ['posts', userId] as const,
445
queryFn: () => fetchUserPosts(userId),
446
enabled: !!userId
447
});
448
449
const createAnalyticsQuery = (dateRange: DateRange) => ({
450
queryKey: ['analytics', dateRange] as const,
451
queryFn: () => fetchAnalytics(dateRange),
452
staleTime: 5 * 60 * 1000
453
});
454
455
// Using query factories
456
function TypeSafeDashboard({ userId, dateRange }: { userId: string; dateRange: DateRange }) {
457
const queries = useQueries({
458
queries: [
459
createUserQuery(userId),
460
createPostsQuery(userId),
461
createAnalyticsQuery(dateRange)
462
]
463
});
464
465
const [userQuery, postsQuery, analyticsQuery] = queries;
466
467
// TypeScript knows the exact types of each query result
468
return (
469
<div>
470
{userQuery.data && <h1>{userQuery.data.name}</h1>}
471
{postsQuery.data && <PostsList posts={postsQuery.data} />}
472
{analyticsQuery.data && <AnalyticsChart data={analyticsQuery.data} />}
473
</div>
474
);
475
}
476
```