CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-tanstack--vue-query

Hooks for managing, caching and syncing asynchronous and remote data in Vue

Pending
Overview
Eval results
Files

status-utilities.mddocs/

Status & Utilities

Utility composables for tracking query and mutation status across the application, providing reactive counters and state monitoring.

Capabilities

useIsFetching

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
});

useIsMutating

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'
});

useMutationState

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
  })
});

Status Monitoring Patterns

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 };
};

Types

// 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

docs

helpers.md

index.md

mutations.md

plugin-setup.md

queries.md

query-client.md

status-utilities.md

tile.json