or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

context.mderror-handling.mdindex.mdinfinite-queries.mdmutations.mdparallel-queries.mdqueries.mdssr.mdstatus.md

parallel-queries.mddocs/

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

```