0
# Context & Client Management
1
2
QueryClient provider system for sharing client instances across component trees with context isolation and configuration. This is the foundation that enables all React Query hooks to work.
3
4
## Capabilities
5
6
### QueryClientProvider Component
7
8
The root provider component that makes QueryClient available to all child components.
9
10
```typescript { .api }
11
/**
12
* Provides QueryClient instance to component tree
13
* @param props - Provider configuration with client and optional context settings
14
* @returns JSX element that wraps children with QueryClient context
15
*/
16
function QueryClientProvider(props: QueryClientProviderProps): JSX.Element;
17
18
type QueryClientProviderProps =
19
| QueryClientProviderPropsWithContext
20
| QueryClientProviderPropsWithContextSharing;
21
22
interface QueryClientProviderPropsWithContext {
23
/** QueryClient instance to provide */
24
client: QueryClient;
25
/** Child components that will have access to QueryClient */
26
children?: React.ReactNode;
27
/** Custom React context to use instead of default */
28
context?: React.Context<QueryClient | undefined>;
29
/** Context sharing must not be used with custom context */
30
contextSharing?: never;
31
}
32
33
interface QueryClientProviderPropsWithContextSharing {
34
/** QueryClient instance to provide */
35
client: QueryClient;
36
/** Child components that will have access to QueryClient */
37
children?: React.ReactNode;
38
/** Custom context cannot be used with context sharing */
39
context?: never;
40
/** Whether to share context across microfrontends/bundles */
41
contextSharing?: boolean;
42
}
43
```
44
45
**Usage Examples:**
46
47
```typescript
48
import { QueryClient, QueryClientProvider } from "react-query";
49
50
// Basic setup
51
const queryClient = new QueryClient();
52
53
function App() {
54
return (
55
<QueryClientProvider client={queryClient}>
56
<div className="App">
57
<Header />
58
<MainContent />
59
<Footer />
60
</div>
61
</QueryClientProvider>
62
);
63
}
64
65
// With configuration
66
const queryClient = new QueryClient({
67
defaultOptions: {
68
queries: {
69
staleTime: 5 * 60 * 1000, // 5 minutes
70
cacheTime: 10 * 60 * 1000, // 10 minutes
71
retry: 2,
72
refetchOnWindowFocus: false
73
},
74
mutations: {
75
retry: 1
76
}
77
}
78
});
79
80
function AppWithDefaults() {
81
return (
82
<QueryClientProvider client={queryClient}>
83
<Router>
84
<Routes>
85
<Route path="/" element={<Home />} />
86
<Route path="/profile" element={<Profile />} />
87
</Routes>
88
</Router>
89
</QueryClientProvider>
90
);
91
}
92
93
// Multiple contexts for different parts of app
94
const mainQueryClient = new QueryClient();
95
const adminQueryClient = new QueryClient({
96
defaultOptions: {
97
queries: { staleTime: 0 } // Always fresh for admin data
98
}
99
});
100
101
function AppWithMultipleContexts() {
102
return (
103
<QueryClientProvider client={mainQueryClient}>
104
<div>
105
<MainApp />
106
<QueryClientProvider client={adminQueryClient}>
107
<AdminPanel />
108
</QueryClientProvider>
109
</div>
110
</QueryClientProvider>
111
);
112
}
113
```
114
115
### useQueryClient Hook
116
117
Hook to access the QueryClient instance from context.
118
119
```typescript { .api }
120
/**
121
* Access QueryClient instance from React context
122
* @param options - Optional context configuration
123
* @returns QueryClient instance
124
* @throws Error if no QueryClient is found in context
125
*/
126
function useQueryClient(options?: ContextOptions): QueryClient;
127
128
interface ContextOptions {
129
/** Custom React context to read from */
130
context?: React.Context<QueryClient | undefined>;
131
}
132
```
133
134
**Usage Examples:**
135
136
```typescript
137
import { useQueryClient } from "react-query";
138
139
// Basic usage
140
function MyComponent() {
141
const queryClient = useQueryClient();
142
143
const handleInvalidate = () => {
144
queryClient.invalidateQueries({ queryKey: ['posts'] });
145
};
146
147
const handleSetData = (newData: any) => {
148
queryClient.setQueryData(['user', 'current'], newData);
149
};
150
151
return (
152
<div>
153
<button onClick={handleInvalidate}>Refresh Posts</button>
154
<button onClick={() => handleSetData({ name: 'New Name' })}>
155
Update User
156
</button>
157
</div>
158
);
159
}
160
161
// With custom context
162
const AdminContext = React.createContext<QueryClient | undefined>(undefined);
163
164
function AdminComponent() {
165
const adminQueryClient = useQueryClient({ context: AdminContext });
166
167
const clearAdminCache = () => {
168
adminQueryClient.clear();
169
};
170
171
return (
172
<button onClick={clearAdminCache}>Clear Admin Cache</button>
173
);
174
}
175
```
176
177
### Default Context
178
179
The default React context used by React Query.
180
181
```typescript { .api }
182
/**
183
* Default React context for QueryClient
184
* Used automatically when no custom context is provided
185
*/
186
const defaultContext: React.Context<QueryClient | undefined>;
187
```
188
189
### QueryClient Class
190
191
The core client class that manages queries and mutations.
192
193
```typescript { .api }
194
/**
195
* Central client for managing queries and mutations
196
* Handles caching, invalidation, and coordination
197
*/
198
class QueryClient {
199
constructor(options?: QueryClientConfig);
200
201
/** Get cached query data */
202
getQueryData<TData = unknown>(queryKey: QueryKey): TData | undefined;
203
204
/** Set query data in cache */
205
setQueryData<TData>(
206
queryKey: QueryKey,
207
data: TData | ((oldData: TData | undefined) => TData),
208
options?: SetDataOptions
209
): TData | undefined;
210
211
/** Get query state information */
212
getQueryState(queryKey: QueryKey): QueryState | undefined;
213
214
/** Invalidate queries to trigger refetch */
215
invalidateQueries(filters?: InvalidateQueryFilters): Promise<void>;
216
217
/** Refetch queries immediately */
218
refetchQueries(filters?: RefetchQueryFilters): Promise<QueryObserverResult[]>;
219
220
/** Fetch query data imperatively */
221
fetchQuery<TQueryFnData = unknown, TError = unknown, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey>(
222
options: FetchQueryOptions<TQueryFnData, TError, TData, TQueryKey>
223
): Promise<TData>;
224
225
/** Fetch query data with separate parameters */
226
fetchQuery<TQueryFnData = unknown, TError = unknown, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey>(
227
queryKey: TQueryKey,
228
queryFn: QueryFunction<TQueryFnData, TQueryKey>,
229
options?: FetchQueryOptions<TQueryFnData, TError, TData, TQueryKey>
230
): Promise<TData>;
231
232
/** Prefetch query data without subscribing */
233
prefetchQuery<TQueryFnData = unknown, TError = unknown, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey>(
234
options: FetchQueryOptions<TQueryFnData, TError, TData, TQueryKey>
235
): Promise<void>;
236
237
/** Prefetch query data with separate parameters */
238
prefetchQuery<TQueryFnData = unknown, TError = unknown, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey>(
239
queryKey: TQueryKey,
240
queryFn: QueryFunction<TQueryFnData, TQueryKey>,
241
options?: FetchQueryOptions<TQueryFnData, TError, TData, TQueryKey>
242
): Promise<void>;
243
244
/** Cancel ongoing queries */
245
cancelQueries(filters?: CancelQueryFilters): Promise<void>;
246
247
/** Remove queries from cache */
248
removeQueries(filters?: RemoveQueryFilters): void;
249
250
/** Clear all cached data */
251
clear(): void;
252
253
/** Execute mutation */
254
executeMutation<TData, TError, TVariables, TContext>(
255
options: MutationOptions<TData, TError, TVariables, TContext>
256
): Promise<TData>;
257
258
/** Check if queries are fetching */
259
isFetching(filters?: QueryFilters): number;
260
261
/** Check if mutations are pending */
262
isMutating(filters?: MutationFilters): number;
263
264
/** Get default query options */
265
defaultQueryOptions<T extends QueryOptions>(options?: T): T;
266
267
/** Get default mutation options */
268
defaultMutationOptions<T extends MutationOptions>(options?: T): T;
269
270
/** Mount client (called automatically by provider) */
271
mount(): void;
272
273
/** Unmount client (called automatically by provider) */
274
unmount(): void;
275
}
276
277
interface QueryClientConfig {
278
/** Default options for all queries */
279
queryCache?: QueryCache;
280
/** Default options for all mutations */
281
mutationCache?: MutationCache;
282
/** Default options for queries */
283
defaultOptions?: {
284
queries?: QueryOptions;
285
mutations?: MutationOptions;
286
};
287
/** Custom logger */
288
logger?: Logger;
289
}
290
291
interface FetchQueryOptions<TQueryFnData = unknown, TError = unknown, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey> extends QueryOptions<TQueryFnData, TError, TData, TQueryKey> {
292
queryKey: TQueryKey;
293
queryFn: QueryFunction<TQueryFnData, TQueryKey>;
294
}
295
296
interface InvalidateQueryFilters<TPageData = unknown> extends QueryFilters {
297
refetchType?: 'active' | 'inactive' | 'all' | 'none';
298
}
299
300
interface RefetchQueryFilters<TPageData = unknown> extends QueryFilters {
301
type?: 'active' | 'inactive' | 'all';
302
}
303
304
interface CancelQueryFilters extends QueryFilters {}
305
306
interface RemoveQueryFilters extends QueryFilters {}
307
308
interface SetDataOptions {
309
updatedAt?: number;
310
}
311
```
312
313
## Advanced Usage Patterns
314
315
### Custom Client Configuration
316
317
Creating QueryClient with specific configurations:
318
319
```typescript
320
import { QueryClient, QueryClientProvider } from "react-query";
321
322
// Production configuration
323
const prodQueryClient = new QueryClient({
324
defaultOptions: {
325
queries: {
326
staleTime: 5 * 60 * 1000, // 5 minutes
327
cacheTime: 10 * 60 * 1000, // 10 minutes
328
retry: (failureCount, error) => {
329
// Don't retry 4xx errors
330
if (error.status >= 400 && error.status < 500) {
331
return false;
332
}
333
return failureCount < 3;
334
},
335
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000)
336
},
337
mutations: {
338
retry: 1,
339
onError: (error) => {
340
// Global error handling
341
console.error('Mutation failed:', error);
342
// Could trigger toast notification, analytics, etc.
343
}
344
}
345
}
346
});
347
348
// Development configuration
349
const devQueryClient = new QueryClient({
350
defaultOptions: {
351
queries: {
352
staleTime: 0, // Always fresh in development
353
cacheTime: 0, // No caching in development
354
retry: false, // Don't retry in development
355
refetchOnWindowFocus: false
356
}
357
},
358
logger: {
359
log: console.log,
360
warn: console.warn,
361
error: console.error
362
}
363
});
364
365
const queryClient = process.env.NODE_ENV === 'production'
366
? prodQueryClient
367
: devQueryClient;
368
```
369
370
### Multiple Client Isolation
371
372
Using separate QueryClients for different application areas:
373
374
```typescript
375
// Separate clients for different data domains
376
const userDataClient = new QueryClient({
377
defaultOptions: {
378
queries: {
379
staleTime: 10 * 60 * 1000, // User data is relatively stable
380
cacheTime: 30 * 60 * 1000
381
}
382
}
383
});
384
385
const realTimeClient = new QueryClient({
386
defaultOptions: {
387
queries: {
388
staleTime: 0, // Real-time data should always be fresh
389
cacheTime: 5 * 60 * 1000,
390
refetchInterval: 30000 // Poll every 30 seconds
391
}
392
}
393
});
394
395
const analyticsClient = new QueryClient({
396
defaultOptions: {
397
queries: {
398
staleTime: 5 * 60 * 1000,
399
cacheTime: 60 * 60 * 1000, // Cache analytics for 1 hour
400
retry: 0 // Don't retry analytics queries
401
}
402
}
403
});
404
405
function App() {
406
return (
407
<QueryClientProvider client={userDataClient}>
408
<div>
409
<UserProfile />
410
411
<QueryClientProvider client={realTimeClient}>
412
<LiveDashboard />
413
</QueryClientProvider>
414
415
<QueryClientProvider client={analyticsClient}>
416
<AnalyticsPanel />
417
</QueryClientProvider>
418
</div>
419
</QueryClientProvider>
420
);
421
}
422
```
423
424
### Manual Cache Management
425
426
Direct cache manipulation using QueryClient:
427
428
```typescript
429
function CacheManager() {
430
const queryClient = useQueryClient();
431
432
const preloadUserData = async (userId: string) => {
433
// Preload user data
434
await queryClient.prefetchQuery({
435
queryKey: ['user', userId],
436
queryFn: () => fetchUser(userId),
437
staleTime: 10 * 60 * 1000
438
});
439
};
440
441
const updateUserInCache = (userId: string, updates: Partial<User>) => {
442
// Update user data in multiple cache locations
443
queryClient.setQueryData(['user', userId], (oldUser: User) => ({
444
...oldUser,
445
...updates
446
}));
447
448
// Update user in lists
449
queryClient.setQueryData(['users'], (oldUsers: User[]) =>
450
oldUsers.map(user =>
451
user.id === userId ? { ...user, ...updates } : user
452
)
453
);
454
};
455
456
const invalidateRelatedData = (userId: string) => {
457
// Invalidate all user-related queries
458
queryClient.invalidateQueries({
459
queryKey: ['user', userId]
460
});
461
462
// Invalidate user posts
463
queryClient.invalidateQueries({
464
queryKey: ['posts'],
465
predicate: (query) => {
466
return query.queryKey.includes(userId);
467
}
468
});
469
};
470
471
const optimisticUpdate = (userId: string, newData: Partial<User>) => {
472
// Store current data for rollback
473
const previousUser = queryClient.getQueryData(['user', userId]);
474
475
// Apply optimistic update
476
queryClient.setQueryData(['user', userId], (old: User) => ({
477
...old,
478
...newData
479
}));
480
481
return () => {
482
// Rollback function
483
queryClient.setQueryData(['user', userId], previousUser);
484
};
485
};
486
487
return (
488
<div>
489
<button onClick={() => preloadUserData('123')}>
490
Preload User 123
491
</button>
492
<button onClick={() => updateUserInCache('123', { name: 'New Name' })}>
493
Update User 123 Name
494
</button>
495
<button onClick={() => invalidateRelatedData('123')}>
496
Refresh User 123 Data
497
</button>
498
</div>
499
);
500
}
501
```
502
503
### Context Sharing for Microfrontends
504
505
Sharing QueryClient across different React applications:
506
507
```typescript
508
// In microfrontend setup
509
function MicrofrontendApp() {
510
const queryClient = new QueryClient();
511
512
return (
513
<QueryClientProvider
514
client={queryClient}
515
contextSharing={true} // Share context across bundles
516
>
517
<MyMicrofrontendContent />
518
</QueryClientProvider>
519
);
520
}
521
522
// This allows different React Query versions/bundles
523
// to share the same QueryClient instance when rendered
524
// in the same window/document
525
```
526
527
### Error Boundaries Integration
528
529
Integrating QueryClient with React error boundaries:
530
531
```typescript
532
class QueryErrorBoundary extends React.Component {
533
constructor(props) {
534
super(props);
535
this.state = { hasError: false };
536
}
537
538
static getDerivedStateFromError(error) {
539
return { hasError: true };
540
}
541
542
componentDidCatch(error, errorInfo) {
543
// Get QueryClient from context if needed
544
const queryClient = this.context;
545
546
// Clear potentially corrupted cache
547
queryClient?.clear();
548
549
// Log error
550
console.error('Query error boundary caught error:', error, errorInfo);
551
}
552
553
render() {
554
if (this.state.hasError) {
555
return (
556
<div>
557
<h2>Something went wrong.</h2>
558
<button onClick={() => this.setState({ hasError: false })}>
559
Try again
560
</button>
561
</div>
562
);
563
}
564
565
return this.props.children;
566
}
567
}
568
569
function AppWithErrorBoundary() {
570
return (
571
<QueryClientProvider client={queryClient}>
572
<QueryErrorBoundary>
573
<App />
574
</QueryErrorBoundary>
575
</QueryClientProvider>
576
);
577
}
578
```