0
# Status & Utilities
1
2
Utility composables for tracking query and mutation status across the application, providing reactive counters and state monitoring.
3
4
## Capabilities
5
6
### useIsFetching
7
8
Composable to track the number of queries currently fetching data.
9
10
```typescript { .api }
11
/**
12
* Track the number of queries currently fetching
13
* @param filters - Optional filters to match specific queries
14
* @param queryClient - Optional query client instance
15
* @returns Reactive ref containing the count of fetching queries
16
*/
17
function useIsFetching(
18
filters?: QueryFilters,
19
queryClient?: QueryClient
20
): Ref<number>;
21
22
interface QueryFilters {
23
exact?: boolean;
24
fetchStatus?: FetchStatus;
25
predicate?: (query: Query) => boolean;
26
queryKey?: QueryKey;
27
stale?: boolean;
28
status?: QueryStatus;
29
type?: QueryTypeFilter;
30
}
31
32
type FetchStatus = 'fetching' | 'paused' | 'idle';
33
type QueryStatus = 'pending' | 'error' | 'success';
34
type QueryTypeFilter = 'all' | 'active' | 'inactive';
35
```
36
37
**Usage Examples:**
38
39
```typescript
40
import { useIsFetching } from '@tanstack/vue-query';
41
42
export default {
43
setup() {
44
// Track all fetching queries
45
const fetchingCount = useIsFetching();
46
47
// Track specific query families
48
const userFetchingCount = useIsFetching({ queryKey: ['user'] });
49
const postsFetchingCount = useIsFetching({ queryKey: ['posts'] });
50
51
// Track queries with specific status
52
const staleFetchingCount = useIsFetching({ stale: true });
53
54
// Show global loading indicator
55
const isGlobalLoading = computed(() => fetchingCount.value > 0);
56
57
// Show section-specific loading
58
const isUserSectionLoading = computed(() => userFetchingCount.value > 0);
59
60
return {
61
fetchingCount,
62
isGlobalLoading,
63
isUserSectionLoading
64
};
65
}
66
};
67
68
// Custom predicate filtering
69
const apiCallsCount = useIsFetching({
70
predicate: (query) => query.queryKey[0] === 'api'
71
});
72
73
// Multiple filters combined
74
const criticalFetchingCount = useIsFetching({
75
queryKey: ['critical'],
76
fetchStatus: 'fetching',
77
stale: false
78
});
79
```
80
81
### useIsMutating
82
83
Composable to track the number of mutations currently pending.
84
85
```typescript { .api }
86
/**
87
* Track the number of mutations currently pending
88
* @param filters - Optional filters to match specific mutations
89
* @param queryClient - Optional query client instance
90
* @returns Reactive ref containing the count of pending mutations
91
*/
92
function useIsMutating(
93
filters?: MutationFilters,
94
queryClient?: QueryClient
95
): Ref<number>;
96
97
interface MutationFilters {
98
exact?: boolean;
99
fetching?: boolean;
100
mutationKey?: MutationKey;
101
predicate?: (mutation: Mutation) => boolean;
102
status?: MutationStatus;
103
}
104
105
type MutationStatus = 'idle' | 'pending' | 'success' | 'error';
106
```
107
108
**Usage Examples:**
109
110
```typescript
111
import { useIsMutating } from '@tanstack/vue-query';
112
113
export default {
114
setup() {
115
// Track all pending mutations
116
const mutatingCount = useIsMutating();
117
118
// Track specific mutation types
119
const savingCount = useIsMutating({ mutationKey: ['save'] });
120
const deletingCount = useIsMutating({ mutationKey: ['delete'] });
121
122
// Track mutations with specific status
123
const pendingMutations = useIsMutating({ status: 'pending' });
124
const erroredMutations = useIsMutating({ status: 'error' });
125
126
// Show global saving indicator
127
const isSaving = computed(() => mutatingCount.value > 0);
128
129
// Show action-specific indicators
130
const isDeleting = computed(() => deletingCount.value > 0);
131
132
// Disable UI during mutations
133
const isFormDisabled = computed(() => savingCount.value > 0);
134
135
return {
136
mutatingCount,
137
isSaving,
138
isDeleting,
139
isFormDisabled
140
};
141
}
142
};
143
144
// Custom predicate filtering
145
const uploadCount = useIsMutating({
146
predicate: (mutation) =>
147
mutation.options.mutationKey?.[0] === 'upload'
148
});
149
150
// Filter by mutation function
151
const postMutationsCount = useIsMutating({
152
predicate: (mutation) =>
153
mutation.options.mutationFn?.name === 'createPost'
154
});
155
```
156
157
### useMutationState
158
159
Composable to access and monitor mutation state across the application.
160
161
```typescript { .api }
162
/**
163
* Access mutation state across the application
164
* @param options - Configuration for selecting and filtering mutations
165
* @param queryClient - Optional query client instance
166
* @returns Readonly ref containing array of selected mutation results
167
*/
168
function useMutationState<TResult = MutationState>(
169
options?: MutationStateOptions<TResult>,
170
queryClient?: QueryClient
171
): Readonly<Ref<Array<TResult>>>;
172
173
interface MutationStateOptions<TResult = MutationState> {
174
filters?: MutationFilters;
175
select?: (mutation: Mutation) => TResult;
176
}
177
178
interface MutationState<TData = unknown, TError = DefaultError, TVariables = unknown, TContext = unknown> {
179
context: TContext | undefined;
180
data: TData | undefined;
181
error: TError | null;
182
failureCount: number;
183
failureReason: TError | null;
184
isPaused: boolean;
185
status: MutationStatus;
186
submittedAt: number;
187
variables: TVariables | undefined;
188
}
189
```
190
191
**Usage Examples:**
192
193
```typescript
194
import { useMutationState } from '@tanstack/vue-query';
195
196
export default {
197
setup() {
198
// Get all mutation states
199
const allMutations = useMutationState();
200
201
// Get mutations with specific filters
202
const saveMutations = useMutationState({
203
filters: { mutationKey: ['save'] }
204
});
205
206
// Get pending mutations only
207
const pendingMutations = useMutationState({
208
filters: { status: 'pending' }
209
});
210
211
// Select specific data from mutations
212
const uploadProgress = useMutationState({
213
filters: { mutationKey: ['upload'] },
214
select: (mutation) => ({
215
id: mutation.mutationId,
216
progress: mutation.context?.progress || 0,
217
filename: mutation.variables?.filename,
218
status: mutation.status
219
})
220
});
221
222
// Get error states for display
223
const mutationErrors = useMutationState({
224
filters: { status: 'error' },
225
select: (mutation) => ({
226
error: mutation.error,
227
variables: mutation.variables,
228
timestamp: mutation.submittedAt
229
})
230
});
231
232
// Recent successful mutations
233
const recentSuccesses = useMutationState({
234
filters: { status: 'success' },
235
select: (mutation) => ({
236
data: mutation.data,
237
completedAt: mutation.submittedAt,
238
variables: mutation.variables
239
})
240
});
241
242
// Compute derived state
243
const hasErrors = computed(() => mutationErrors.value.length > 0);
244
const overallProgress = computed(() => {
245
const uploads = uploadProgress.value;
246
if (uploads.length === 0) return 0;
247
const total = uploads.reduce((sum, upload) => sum + upload.progress, 0);
248
return total / uploads.length;
249
});
250
251
return {
252
allMutations,
253
uploadProgress,
254
mutationErrors,
255
hasErrors,
256
overallProgress
257
};
258
}
259
};
260
261
// Real-time activity monitoring
262
const activityFeed = useMutationState({
263
select: (mutation) => ({
264
id: mutation.mutationId,
265
type: mutation.options.mutationKey?.[0],
266
status: mutation.status,
267
timestamp: mutation.submittedAt,
268
error: mutation.error?.message,
269
data: mutation.data
270
})
271
});
272
273
// Sort by most recent
274
const sortedActivity = computed(() =>
275
[...activityFeed.value].sort((a, b) => b.timestamp - a.timestamp)
276
);
277
278
// Filter by time range
279
const recentActivity = computed(() => {
280
const hourAgo = Date.now() - 60 * 60 * 1000;
281
return activityFeed.value.filter(item => item.timestamp > hourAgo);
282
});
283
284
// Form submission tracking
285
const formSubmissions = useMutationState({
286
filters: {
287
predicate: (mutation) =>
288
mutation.options.mutationKey?.[0] === 'form' &&
289
mutation.status === 'pending'
290
},
291
select: (mutation) => ({
292
formId: mutation.variables?.formId,
293
submittedAt: mutation.submittedAt,
294
progress: mutation.context?.uploadProgress
295
})
296
});
297
```
298
299
## Status Monitoring Patterns
300
301
**Usage Examples:**
302
303
```typescript
304
// Global application status
305
export default {
306
setup() {
307
const isFetching = useIsFetching();
308
const isMutating = useIsMutating();
309
310
// Application busy state
311
const isAppBusy = computed(() =>
312
isFetching.value > 0 || isMutating.value > 0
313
);
314
315
// Critical operations status
316
const criticalFetching = useIsFetching({
317
predicate: query => query.meta?.critical === true
318
});
319
320
const criticalMutating = useIsMutating({
321
predicate: mutation => mutation.meta?.critical === true
322
});
323
324
const isCriticalBusy = computed(() =>
325
criticalFetching.value > 0 || criticalMutating.value > 0
326
);
327
328
// Show different loading states
329
const loadingState = computed(() => {
330
if (isCriticalBusy.value) return 'critical';
331
if (isAppBusy.value) return 'busy';
332
return 'idle';
333
});
334
335
return {
336
loadingState,
337
isAppBusy,
338
isCriticalBusy
339
};
340
}
341
};
342
343
// Error aggregation
344
const useErrorSummary = () => {
345
const errorMutations = useMutationState({
346
filters: { status: 'error' },
347
select: mutation => ({
348
type: mutation.options.mutationKey?.[0],
349
error: mutation.error,
350
timestamp: mutation.submittedAt
351
})
352
});
353
354
const errorSummary = computed(() => {
355
const errors = errorMutations.value;
356
const byType = errors.reduce((acc, err) => {
357
const type = err.type || 'unknown';
358
acc[type] = (acc[type] || 0) + 1;
359
return acc;
360
}, {});
361
362
return {
363
total: errors.length,
364
byType,
365
recent: errors.filter(err =>
366
Date.now() - err.timestamp < 5 * 60 * 1000 // Last 5 minutes
367
)
368
};
369
});
370
371
return { errorSummary };
372
};
373
374
// Progress tracking
375
const useUploadProgress = () => {
376
const uploads = useMutationState({
377
filters: { mutationKey: ['upload'] },
378
select: mutation => ({
379
id: mutation.mutationId,
380
filename: mutation.variables?.filename,
381
progress: mutation.context?.progress || 0,
382
status: mutation.status,
383
error: mutation.error
384
})
385
});
386
387
const overallProgress = computed(() => {
388
const activeUploads = uploads.value.filter(u => u.status === 'pending');
389
if (activeUploads.length === 0) return 100;
390
391
const totalProgress = activeUploads.reduce((sum, upload) =>
392
sum + upload.progress, 0
393
);
394
return Math.round(totalProgress / activeUploads.length);
395
});
396
397
return { uploads, overallProgress };
398
};
399
```
400
401
## Types
402
403
```typescript { .api }
404
// Filter types
405
interface QueryFilters {
406
exact?: boolean;
407
fetchStatus?: FetchStatus;
408
predicate?: (query: Query) => boolean;
409
queryKey?: QueryKey;
410
stale?: boolean;
411
status?: QueryStatus;
412
type?: QueryTypeFilter;
413
}
414
415
interface MutationFilters {
416
exact?: boolean;
417
fetching?: boolean;
418
mutationKey?: MutationKey;
419
predicate?: (mutation: Mutation) => boolean;
420
status?: MutationStatus;
421
}
422
423
// State types
424
type FetchStatus = 'fetching' | 'paused' | 'idle';
425
type QueryStatus = 'pending' | 'error' | 'success';
426
type MutationStatus = 'idle' | 'pending' | 'success' | 'error';
427
type QueryTypeFilter = 'all' | 'active' | 'inactive';
428
429
// Mutation state interface
430
interface MutationState<TData = unknown, TError = DefaultError, TVariables = unknown, TContext = unknown> {
431
context: TContext | undefined;
432
data: TData | undefined;
433
error: TError | null;
434
failureCount: number;
435
failureReason: TError | null;
436
isPaused: boolean;
437
status: MutationStatus;
438
submittedAt: number;
439
variables: TVariables | undefined;
440
}
441
442
// Options interfaces
443
interface MutationStateOptions<TResult = MutationState> {
444
filters?: MutationFilters;
445
select?: (mutation: Mutation) => TResult;
446
}
447
448
type DefaultError = Error;
449
```