0
# Status Monitoring
1
2
Hooks for monitoring global query and mutation states across the application. These utilities help track loading states, show global loading indicators, and monitor application activity.
3
4
## Capabilities
5
6
### useIsFetching Hook
7
8
Monitor the number of queries currently fetching across the application.
9
10
```typescript { .api }
11
/**
12
* Get count of currently fetching queries
13
* @param filters - Optional filters to target specific queries
14
* @param options - Optional context configuration
15
* @returns Number of queries currently fetching
16
*/
17
function useIsFetching(filters?: QueryFilters, options?: ContextOptions): number;
18
19
/**
20
* Get count of currently fetching queries with queryKey filter
21
* @param queryKey - Query key to filter by
22
* @param filters - Additional filters
23
* @param options - Optional context configuration
24
* @returns Number of matching queries currently fetching
25
*/
26
function useIsFetching(
27
queryKey?: QueryKey,
28
filters?: QueryFilters,
29
options?: ContextOptions
30
): number;
31
```
32
33
**Usage Examples:**
34
35
```typescript
36
import { useIsFetching } from "react-query";
37
38
// Global loading indicator
39
function GlobalLoadingIndicator() {
40
const isFetching = useIsFetching();
41
42
if (isFetching) {
43
return (
44
<div className="global-loading">
45
<div className="spinner" />
46
Loading data... ({isFetching} active requests)
47
</div>
48
);
49
}
50
51
return null;
52
}
53
54
// Specific query type loading
55
function UserDataLoadingIndicator({ userId }: { userId: string }) {
56
const userQueriesFetching = useIsFetching({
57
queryKey: ['user', userId]
58
});
59
60
const isUserDataLoading = userQueriesFetching > 0;
61
62
return isUserDataLoading ? (
63
<div className="user-loading">Updating user data...</div>
64
) : null;
65
}
66
67
// Filter by query type
68
function PostsLoadingIndicator() {
69
const postsLoading = useIsFetching({
70
queryKey: ['posts'],
71
exact: false // Match any query starting with ['posts']
72
});
73
74
const analyticsLoading = useIsFetching({
75
queryKey: ['analytics'],
76
exact: false
77
});
78
79
return (
80
<div className="loading-status">
81
{postsLoading > 0 && <span>Posts loading...</span>}
82
{analyticsLoading > 0 && <span>Analytics loading...</span>}
83
</div>
84
);
85
}
86
```
87
88
### useIsMutating Hook
89
90
Monitor the number of mutations currently in progress across the application.
91
92
```typescript { .api }
93
/**
94
* Get count of currently running mutations
95
* @param filters - Optional filters to target specific mutations
96
* @param options - Optional context configuration
97
* @returns Number of mutations currently running
98
*/
99
function useIsMutating(
100
filters?: MutationFilters,
101
options?: ContextOptions
102
): number;
103
104
/**
105
* Get count of currently running mutations with mutationKey filter
106
* @param mutationKey - Mutation key to filter by
107
* @param filters - Additional filters excluding mutationKey
108
* @param options - Optional context configuration
109
* @returns Number of matching mutations currently running
110
*/
111
function useIsMutating(
112
mutationKey?: MutationKey,
113
filters?: Omit<MutationFilters, 'mutationKey'>,
114
options?: ContextOptions
115
): number;
116
```
117
118
**Usage Examples:**
119
120
```typescript
121
import { useIsMutating } from "react-query";
122
123
// Global mutation indicator
124
function GlobalSavingIndicator() {
125
const isMutating = useIsMutating();
126
127
if (isMutating) {
128
return (
129
<div className="global-saving">
130
<div className="pulse-dot" />
131
Saving changes... ({isMutating} operations)
132
</div>
133
);
134
}
135
136
return null;
137
}
138
139
// Specific mutation type
140
function UserUpdateIndicator({ userId }: { userId: string }) {
141
const isUpdatingUser = useIsMutating({
142
mutationKey: ['updateUser', userId]
143
});
144
145
return isUpdatingUser > 0 ? (
146
<div className="inline-saving">
147
<div className="spinner-small" />
148
Saving...
149
</div>
150
) : null;
151
}
152
153
// Multiple mutation types
154
function FormSavingStatus() {
155
const userMutations = useIsMutating({
156
mutationKey: ['user']
157
});
158
159
const settingsMutations = useIsMutating({
160
mutationKey: ['settings']
161
});
162
163
const profileMutations = useIsMutating({
164
mutationKey: ['profile']
165
});
166
167
const totalMutations = userMutations + settingsMutations + profileMutations;
168
169
if (totalMutations === 0) return null;
170
171
return (
172
<div className="form-status">
173
<div className="saving-indicator">
174
<span>Saving</span>
175
{userMutations > 0 && <span className="tag">User</span>}
176
{settingsMutations > 0 && <span className="tag">Settings</span>}
177
{profileMutations > 0 && <span className="tag">Profile</span>}
178
</div>
179
</div>
180
);
181
}
182
```
183
184
### Filter Types
185
186
Types for filtering queries and mutations in status monitoring.
187
188
```typescript { .api }
189
interface QueryFilters {
190
/** Query key to match */
191
queryKey?: QueryKey;
192
/** Whether to match query key exactly */
193
exact?: boolean;
194
/** Query type filter */
195
type?: 'active' | 'inactive' | 'all';
196
/** Whether to include stale queries */
197
stale?: boolean;
198
/** Whether to include fetching queries */
199
fetching?: boolean;
200
/** Custom predicate function */
201
predicate?: (query: Query) => boolean;
202
}
203
204
interface MutationFilters {
205
/** Mutation key to match */
206
mutationKey?: MutationKey;
207
/** Whether to match mutation key exactly */
208
exact?: boolean;
209
/** Mutation type filter */
210
type?: 'active' | 'paused' | 'all';
211
/** Custom predicate function */
212
predicate?: (mutation: Mutation) => boolean;
213
}
214
215
interface ContextOptions {
216
/** Custom React context to use */
217
context?: React.Context<QueryClient | undefined>;
218
}
219
220
type QueryKey = readonly unknown[];
221
type MutationKey = readonly unknown[];
222
```
223
224
## Advanced Usage Patterns
225
226
### Application-Wide Activity Monitor
227
228
Comprehensive activity monitoring across the entire application:
229
230
```typescript
231
function ApplicationActivityMonitor() {
232
const fetchingCount = useIsFetching();
233
const mutatingCount = useIsMutating();
234
235
const isActive = fetchingCount > 0 || mutatingCount > 0;
236
237
// Track activity for analytics
238
useEffect(() => {
239
if (isActive) {
240
analytics.track('app_activity_started', {
241
queries: fetchingCount,
242
mutations: mutatingCount
243
});
244
} else {
245
analytics.track('app_activity_stopped');
246
}
247
}, [isActive, fetchingCount, mutatingCount]);
248
249
// Prevent page unload during mutations
250
useEffect(() => {
251
if (mutatingCount > 0) {
252
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
253
e.preventDefault();
254
e.returnValue = 'You have unsaved changes. Are you sure you want to leave?';
255
return e.returnValue;
256
};
257
258
window.addEventListener('beforeunload', handleBeforeUnload);
259
return () => window.removeEventListener('beforeunload', handleBeforeUnload);
260
}
261
}, [mutatingCount]);
262
263
return (
264
<div className="activity-monitor">
265
<div className="status-bar">
266
{fetchingCount > 0 && (
267
<div className="fetching-status">
268
<div className="loading-icon" />
269
{fetchingCount} loading
270
</div>
271
)}
272
273
{mutatingCount > 0 && (
274
<div className="mutating-status">
275
<div className="saving-icon" />
276
{mutatingCount} saving
277
</div>
278
)}
279
280
{!isActive && (
281
<div className="idle-status">
282
<div className="idle-icon" />
283
All up to date
284
</div>
285
)}
286
</div>
287
</div>
288
);
289
}
290
```
291
292
### Feature-Specific Loading States
293
294
Monitoring specific application features:
295
296
```typescript
297
interface FeatureLoadingStates {
298
dashboard: number;
299
profile: number;
300
notifications: number;
301
analytics: number;
302
}
303
304
function useFeatureLoadingStates(): FeatureLoadingStates {
305
const dashboardLoading = useIsFetching({
306
queryKey: ['dashboard'],
307
exact: false
308
});
309
310
const profileLoading = useIsFetching({
311
queryKey: ['profile'],
312
exact: false
313
}) + useIsMutating({
314
mutationKey: ['profile'],
315
exact: false
316
});
317
318
const notificationsLoading = useIsFetching({
319
queryKey: ['notifications'],
320
exact: false
321
});
322
323
const analyticsLoading = useIsFetching({
324
queryKey: ['analytics'],
325
exact: false
326
});
327
328
return {
329
dashboard: dashboardLoading,
330
profile: profileLoading,
331
notifications: notificationsLoading,
332
analytics: analyticsLoading
333
};
334
}
335
336
function FeatureStatusPanel() {
337
const loadingStates = useFeatureLoadingStates();
338
339
const features = [
340
{ name: 'Dashboard', count: loadingStates.dashboard },
341
{ name: 'Profile', count: loadingStates.profile },
342
{ name: 'Notifications', count: loadingStates.notifications },
343
{ name: 'Analytics', count: loadingStates.analytics }
344
];
345
346
return (
347
<div className="feature-status">
348
<h3>Feature Status</h3>
349
{features.map(feature => (
350
<div key={feature.name} className="feature-item">
351
<span>{feature.name}</span>
352
<span className={feature.count > 0 ? 'loading' : 'idle'}>
353
{feature.count > 0 ? `Loading (${feature.count})` : 'Ready'}
354
</span>
355
</div>
356
))}
357
</div>
358
);
359
}
360
```
361
362
### Performance Monitoring
363
364
Tracking query and mutation performance:
365
366
```typescript
367
function usePerformanceMonitoring() {
368
const [metrics, setMetrics] = useState({
369
activeQueries: 0,
370
activeMutations: 0,
371
peakQueries: 0,
372
peakMutations: 0,
373
totalQueries: 0,
374
totalMutations: 0
375
});
376
377
const currentQueries = useIsFetching();
378
const currentMutations = useIsMutating();
379
380
useEffect(() => {
381
setMetrics(prev => ({
382
...prev,
383
activeQueries: currentQueries,
384
activeMutations: currentMutations,
385
peakQueries: Math.max(prev.peakQueries, currentQueries),
386
peakMutations: Math.max(prev.peakMutations, currentMutations)
387
}));
388
}, [currentQueries, currentMutations]);
389
390
// Track when queries/mutations start and complete
391
const prevQueries = useRef(0);
392
const prevMutations = useRef(0);
393
394
useEffect(() => {
395
if (currentQueries > prevQueries.current) {
396
setMetrics(prev => ({
397
...prev,
398
totalQueries: prev.totalQueries + (currentQueries - prevQueries.current)
399
}));
400
}
401
prevQueries.current = currentQueries;
402
}, [currentQueries]);
403
404
useEffect(() => {
405
if (currentMutations > prevMutations.current) {
406
setMetrics(prev => ({
407
...prev,
408
totalMutations: prev.totalMutations + (currentMutations - prevMutations.current)
409
}));
410
}
411
prevMutations.current = currentMutations;
412
}, [currentMutations]);
413
414
return metrics;
415
}
416
417
function PerformancePanel() {
418
const metrics = usePerformanceMonitoring();
419
420
return (
421
<div className="performance-panel">
422
<h3>Performance Metrics</h3>
423
<div className="metrics-grid">
424
<div className="metric">
425
<label>Active Queries</label>
426
<span>{metrics.activeQueries}</span>
427
</div>
428
<div className="metric">
429
<label>Active Mutations</label>
430
<span>{metrics.activeMutations}</span>
431
</div>
432
<div className="metric">
433
<label>Peak Queries</label>
434
<span>{metrics.peakQueries}</span>
435
</div>
436
<div className="metric">
437
<label>Peak Mutations</label>
438
<span>{metrics.peakMutations}</span>
439
</div>
440
<div className="metric">
441
<label>Total Queries</label>
442
<span>{metrics.totalQueries}</span>
443
</div>
444
<div className="metric">
445
<label>Total Mutations</label>
446
<span>{metrics.totalMutations}</span>
447
</div>
448
</div>
449
</div>
450
);
451
}
452
```
453
454
### Conditional UI Based on Activity
455
456
Adapting UI behavior based on current activity:
457
458
```typescript
459
function AdaptiveUI({ children }: { children: React.ReactNode }) {
460
const isFetching = useIsFetching();
461
const isMutating = useIsMutating();
462
463
const isActive = isFetching > 0 || isMutating > 0;
464
const isHeavyActivity = isFetching > 3 || isMutating > 2;
465
466
return (
467
<div className={`app-container ${isActive ? 'activity-mode' : 'idle-mode'}`}>
468
{/* Reduce animations during heavy activity */}
469
<style>
470
{isHeavyActivity && `
471
.app-container * {
472
animation-duration: 0.1s !important;
473
transition-duration: 0.1s !important;
474
}
475
`}
476
</style>
477
478
{/* Activity overlay */}
479
{isActive && (
480
<div className="activity-overlay">
481
<div className="activity-indicator">
482
{isFetching > 0 && <span>Loading data...</span>}
483
{isMutating > 0 && <span>Saving changes...</span>}
484
</div>
485
</div>
486
)}
487
488
{/* Disable interactions during critical mutations */}
489
<div className={isMutating > 0 ? 'pointer-events-none' : ''}>
490
{children}
491
</div>
492
</div>
493
);
494
}
495
496
function NetworkStatusBar() {
497
const queryCount = useIsFetching();
498
const mutationCount = useIsMutating();
499
500
// Show different indicators based on activity type
501
if (mutationCount > 0) {
502
return (
503
<div className="status-bar saving">
504
<div className="icon-saving" />
505
<span>Saving {mutationCount} change{mutationCount !== 1 ? 's' : ''}...</span>
506
</div>
507
);
508
}
509
510
if (queryCount > 0) {
511
return (
512
<div className="status-bar loading">
513
<div className="icon-loading" />
514
<span>Loading {queryCount} request{queryCount !== 1 ? 's' : ''}...</span>
515
</div>
516
);
517
}
518
519
return (
520
<div className="status-bar idle">
521
<div className="icon-idle" />
522
<span>All data up to date</span>
523
</div>
524
);
525
}
526
```