Hooks for managing, caching and syncing asynchronous and remote data in Vue
—
Utility composables for tracking query and mutation status across the application, providing reactive counters and state monitoring.
Composable to track the number of queries currently fetching data.
/**
* Track the number of queries currently fetching
* @param filters - Optional filters to match specific queries
* @param queryClient - Optional query client instance
* @returns Reactive ref containing the count of fetching queries
*/
function useIsFetching(
filters?: QueryFilters,
queryClient?: QueryClient
): Ref<number>;
interface QueryFilters {
exact?: boolean;
fetchStatus?: FetchStatus;
predicate?: (query: Query) => boolean;
queryKey?: QueryKey;
stale?: boolean;
status?: QueryStatus;
type?: QueryTypeFilter;
}
type FetchStatus = 'fetching' | 'paused' | 'idle';
type QueryStatus = 'pending' | 'error' | 'success';
type QueryTypeFilter = 'all' | 'active' | 'inactive';Usage Examples:
import { useIsFetching } from '@tanstack/vue-query';
export default {
setup() {
// Track all fetching queries
const fetchingCount = useIsFetching();
// Track specific query families
const userFetchingCount = useIsFetching({ queryKey: ['user'] });
const postsFetchingCount = useIsFetching({ queryKey: ['posts'] });
// Track queries with specific status
const staleFetchingCount = useIsFetching({ stale: true });
// Show global loading indicator
const isGlobalLoading = computed(() => fetchingCount.value > 0);
// Show section-specific loading
const isUserSectionLoading = computed(() => userFetchingCount.value > 0);
return {
fetchingCount,
isGlobalLoading,
isUserSectionLoading
};
}
};
// Custom predicate filtering
const apiCallsCount = useIsFetching({
predicate: (query) => query.queryKey[0] === 'api'
});
// Multiple filters combined
const criticalFetchingCount = useIsFetching({
queryKey: ['critical'],
fetchStatus: 'fetching',
stale: false
});Composable to track the number of mutations currently pending.
/**
* Track the number of mutations currently pending
* @param filters - Optional filters to match specific mutations
* @param queryClient - Optional query client instance
* @returns Reactive ref containing the count of pending mutations
*/
function useIsMutating(
filters?: MutationFilters,
queryClient?: QueryClient
): Ref<number>;
interface MutationFilters {
exact?: boolean;
fetching?: boolean;
mutationKey?: MutationKey;
predicate?: (mutation: Mutation) => boolean;
status?: MutationStatus;
}
type MutationStatus = 'idle' | 'pending' | 'success' | 'error';Usage Examples:
import { useIsMutating } from '@tanstack/vue-query';
export default {
setup() {
// Track all pending mutations
const mutatingCount = useIsMutating();
// Track specific mutation types
const savingCount = useIsMutating({ mutationKey: ['save'] });
const deletingCount = useIsMutating({ mutationKey: ['delete'] });
// Track mutations with specific status
const pendingMutations = useIsMutating({ status: 'pending' });
const erroredMutations = useIsMutating({ status: 'error' });
// Show global saving indicator
const isSaving = computed(() => mutatingCount.value > 0);
// Show action-specific indicators
const isDeleting = computed(() => deletingCount.value > 0);
// Disable UI during mutations
const isFormDisabled = computed(() => savingCount.value > 0);
return {
mutatingCount,
isSaving,
isDeleting,
isFormDisabled
};
}
};
// Custom predicate filtering
const uploadCount = useIsMutating({
predicate: (mutation) =>
mutation.options.mutationKey?.[0] === 'upload'
});
// Filter by mutation function
const postMutationsCount = useIsMutating({
predicate: (mutation) =>
mutation.options.mutationFn?.name === 'createPost'
});Composable to access and monitor mutation state across the application.
/**
* Access mutation state across the application
* @param options - Configuration for selecting and filtering mutations
* @param queryClient - Optional query client instance
* @returns Readonly ref containing array of selected mutation results
*/
function useMutationState<TResult = MutationState>(
options?: MutationStateOptions<TResult>,
queryClient?: QueryClient
): Readonly<Ref<Array<TResult>>>;
interface MutationStateOptions<TResult = MutationState> {
filters?: MutationFilters;
select?: (mutation: Mutation) => TResult;
}
interface MutationState<TData = unknown, TError = DefaultError, TVariables = unknown, TContext = unknown> {
context: TContext | undefined;
data: TData | undefined;
error: TError | null;
failureCount: number;
failureReason: TError | null;
isPaused: boolean;
status: MutationStatus;
submittedAt: number;
variables: TVariables | undefined;
}Usage Examples:
import { useMutationState } from '@tanstack/vue-query';
export default {
setup() {
// Get all mutation states
const allMutations = useMutationState();
// Get mutations with specific filters
const saveMutations = useMutationState({
filters: { mutationKey: ['save'] }
});
// Get pending mutations only
const pendingMutations = useMutationState({
filters: { status: 'pending' }
});
// Select specific data from mutations
const uploadProgress = useMutationState({
filters: { mutationKey: ['upload'] },
select: (mutation) => ({
id: mutation.mutationId,
progress: mutation.context?.progress || 0,
filename: mutation.variables?.filename,
status: mutation.status
})
});
// Get error states for display
const mutationErrors = useMutationState({
filters: { status: 'error' },
select: (mutation) => ({
error: mutation.error,
variables: mutation.variables,
timestamp: mutation.submittedAt
})
});
// Recent successful mutations
const recentSuccesses = useMutationState({
filters: { status: 'success' },
select: (mutation) => ({
data: mutation.data,
completedAt: mutation.submittedAt,
variables: mutation.variables
})
});
// Compute derived state
const hasErrors = computed(() => mutationErrors.value.length > 0);
const overallProgress = computed(() => {
const uploads = uploadProgress.value;
if (uploads.length === 0) return 0;
const total = uploads.reduce((sum, upload) => sum + upload.progress, 0);
return total / uploads.length;
});
return {
allMutations,
uploadProgress,
mutationErrors,
hasErrors,
overallProgress
};
}
};
// Real-time activity monitoring
const activityFeed = useMutationState({
select: (mutation) => ({
id: mutation.mutationId,
type: mutation.options.mutationKey?.[0],
status: mutation.status,
timestamp: mutation.submittedAt,
error: mutation.error?.message,
data: mutation.data
})
});
// Sort by most recent
const sortedActivity = computed(() =>
[...activityFeed.value].sort((a, b) => b.timestamp - a.timestamp)
);
// Filter by time range
const recentActivity = computed(() => {
const hourAgo = Date.now() - 60 * 60 * 1000;
return activityFeed.value.filter(item => item.timestamp > hourAgo);
});
// Form submission tracking
const formSubmissions = useMutationState({
filters: {
predicate: (mutation) =>
mutation.options.mutationKey?.[0] === 'form' &&
mutation.status === 'pending'
},
select: (mutation) => ({
formId: mutation.variables?.formId,
submittedAt: mutation.submittedAt,
progress: mutation.context?.uploadProgress
})
});Usage Examples:
// Global application status
export default {
setup() {
const isFetching = useIsFetching();
const isMutating = useIsMutating();
// Application busy state
const isAppBusy = computed(() =>
isFetching.value > 0 || isMutating.value > 0
);
// Critical operations status
const criticalFetching = useIsFetching({
predicate: query => query.meta?.critical === true
});
const criticalMutating = useIsMutating({
predicate: mutation => mutation.meta?.critical === true
});
const isCriticalBusy = computed(() =>
criticalFetching.value > 0 || criticalMutating.value > 0
);
// Show different loading states
const loadingState = computed(() => {
if (isCriticalBusy.value) return 'critical';
if (isAppBusy.value) return 'busy';
return 'idle';
});
return {
loadingState,
isAppBusy,
isCriticalBusy
};
}
};
// Error aggregation
const useErrorSummary = () => {
const errorMutations = useMutationState({
filters: { status: 'error' },
select: mutation => ({
type: mutation.options.mutationKey?.[0],
error: mutation.error,
timestamp: mutation.submittedAt
})
});
const errorSummary = computed(() => {
const errors = errorMutations.value;
const byType = errors.reduce((acc, err) => {
const type = err.type || 'unknown';
acc[type] = (acc[type] || 0) + 1;
return acc;
}, {});
return {
total: errors.length,
byType,
recent: errors.filter(err =>
Date.now() - err.timestamp < 5 * 60 * 1000 // Last 5 minutes
)
};
});
return { errorSummary };
};
// Progress tracking
const useUploadProgress = () => {
const uploads = useMutationState({
filters: { mutationKey: ['upload'] },
select: mutation => ({
id: mutation.mutationId,
filename: mutation.variables?.filename,
progress: mutation.context?.progress || 0,
status: mutation.status,
error: mutation.error
})
});
const overallProgress = computed(() => {
const activeUploads = uploads.value.filter(u => u.status === 'pending');
if (activeUploads.length === 0) return 100;
const totalProgress = activeUploads.reduce((sum, upload) =>
sum + upload.progress, 0
);
return Math.round(totalProgress / activeUploads.length);
});
return { uploads, overallProgress };
};// Filter types
interface QueryFilters {
exact?: boolean;
fetchStatus?: FetchStatus;
predicate?: (query: Query) => boolean;
queryKey?: QueryKey;
stale?: boolean;
status?: QueryStatus;
type?: QueryTypeFilter;
}
interface MutationFilters {
exact?: boolean;
fetching?: boolean;
mutationKey?: MutationKey;
predicate?: (mutation: Mutation) => boolean;
status?: MutationStatus;
}
// State types
type FetchStatus = 'fetching' | 'paused' | 'idle';
type QueryStatus = 'pending' | 'error' | 'success';
type MutationStatus = 'idle' | 'pending' | 'success' | 'error';
type QueryTypeFilter = 'all' | 'active' | 'inactive';
// Mutation state interface
interface MutationState<TData = unknown, TError = DefaultError, TVariables = unknown, TContext = unknown> {
context: TContext | undefined;
data: TData | undefined;
error: TError | null;
failureCount: number;
failureReason: TError | null;
isPaused: boolean;
status: MutationStatus;
submittedAt: number;
variables: TVariables | undefined;
}
// Options interfaces
interface MutationStateOptions<TResult = MutationState> {
filters?: MutationFilters;
select?: (mutation: Mutation) => TResult;
}
type DefaultError = Error;Install with Tessl CLI
npx tessl i tessl/npm-tanstack--vue-query