CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-query

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

Pending
Overview
Eval results
Files

status.mddocs/

Status Monitoring

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.

Capabilities

useIsFetching Hook

Monitor the number of queries currently fetching across the application.

/**
 * Get count of currently fetching queries
 * @param filters - Optional filters to target specific queries
 * @param options - Optional context configuration
 * @returns Number of queries currently fetching
 */
function useIsFetching(filters?: QueryFilters, options?: ContextOptions): number;

/**
 * Get count of currently fetching queries with queryKey filter
 * @param queryKey - Query key to filter by
 * @param filters - Additional filters
 * @param options - Optional context configuration
 * @returns Number of matching queries currently fetching
 */
function useIsFetching(
  queryKey?: QueryKey,
  filters?: QueryFilters,
  options?: ContextOptions
): number;

Usage Examples:

import { useIsFetching } from "react-query";

// Global loading indicator
function GlobalLoadingIndicator() {
  const isFetching = useIsFetching();

  if (isFetching) {
    return (
      <div className="global-loading">
        <div className="spinner" />
        Loading data... ({isFetching} active requests)
      </div>
    );
  }

  return null;
}

// Specific query type loading
function UserDataLoadingIndicator({ userId }: { userId: string }) {
  const userQueriesFetching = useIsFetching({
    queryKey: ['user', userId]
  });

  const isUserDataLoading = userQueriesFetching > 0;

  return isUserDataLoading ? (
    <div className="user-loading">Updating user data...</div>
  ) : null;
}

// Filter by query type
function PostsLoadingIndicator() {
  const postsLoading = useIsFetching({
    queryKey: ['posts'],
    exact: false // Match any query starting with ['posts']
  });

  const analyticsLoading = useIsFetching({
    queryKey: ['analytics'],
    exact: false
  });

  return (
    <div className="loading-status">
      {postsLoading > 0 && <span>Posts loading...</span>}
      {analyticsLoading > 0 && <span>Analytics loading...</span>}
    </div>
  );
}

useIsMutating Hook

Monitor the number of mutations currently in progress across the application.

/**
 * Get count of currently running mutations
 * @param filters - Optional filters to target specific mutations
 * @param options - Optional context configuration
 * @returns Number of mutations currently running
 */
function useIsMutating(
  filters?: MutationFilters,
  options?: ContextOptions
): number;

/**
 * Get count of currently running mutations with mutationKey filter
 * @param mutationKey - Mutation key to filter by
 * @param filters - Additional filters excluding mutationKey
 * @param options - Optional context configuration
 * @returns Number of matching mutations currently running
 */
function useIsMutating(
  mutationKey?: MutationKey,
  filters?: Omit<MutationFilters, 'mutationKey'>,
  options?: ContextOptions
): number;

Usage Examples:

import { useIsMutating } from "react-query";

// Global mutation indicator
function GlobalSavingIndicator() {
  const isMutating = useIsMutating();

  if (isMutating) {
    return (
      <div className="global-saving">
        <div className="pulse-dot" />
        Saving changes... ({isMutating} operations)
      </div>
    );
  }

  return null;
}

// Specific mutation type
function UserUpdateIndicator({ userId }: { userId: string }) {
  const isUpdatingUser = useIsMutating({
    mutationKey: ['updateUser', userId]
  });

  return isUpdatingUser > 0 ? (
    <div className="inline-saving">
      <div className="spinner-small" />
      Saving...
    </div>
  ) : null;
}

// Multiple mutation types
function FormSavingStatus() {
  const userMutations = useIsMutating({
    mutationKey: ['user']
  });

  const settingsMutations = useIsMutating({
    mutationKey: ['settings']
  });

  const profileMutations = useIsMutating({
    mutationKey: ['profile']
  });

  const totalMutations = userMutations + settingsMutations + profileMutations;

  if (totalMutations === 0) return null;

  return (
    <div className="form-status">
      <div className="saving-indicator">
        <span>Saving</span>
        {userMutations > 0 && <span className="tag">User</span>}
        {settingsMutations > 0 && <span className="tag">Settings</span>}
        {profileMutations > 0 && <span className="tag">Profile</span>}
      </div>
    </div>
  );
}

Filter Types

Types for filtering queries and mutations in status monitoring.

interface QueryFilters {
  /** Query key to match */
  queryKey?: QueryKey;
  /** Whether to match query key exactly */
  exact?: boolean;
  /** Query type filter */
  type?: 'active' | 'inactive' | 'all';
  /** Whether to include stale queries */
  stale?: boolean;
  /** Whether to include fetching queries */
  fetching?: boolean;
  /** Custom predicate function */
  predicate?: (query: Query) => boolean;
}

interface MutationFilters {
  /** Mutation key to match */
  mutationKey?: MutationKey;
  /** Whether to match mutation key exactly */
  exact?: boolean;
  /** Mutation type filter */
  type?: 'active' | 'paused' | 'all';
  /** Custom predicate function */
  predicate?: (mutation: Mutation) => boolean;
}

interface ContextOptions {
  /** Custom React context to use */
  context?: React.Context<QueryClient | undefined>;
}

type QueryKey = readonly unknown[];
type MutationKey = readonly unknown[];

Advanced Usage Patterns

Application-Wide Activity Monitor

Comprehensive activity monitoring across the entire application:

function ApplicationActivityMonitor() {
  const fetchingCount = useIsFetching();
  const mutatingCount = useIsMutating();

  const isActive = fetchingCount > 0 || mutatingCount > 0;

  // Track activity for analytics
  useEffect(() => {
    if (isActive) {
      analytics.track('app_activity_started', {
        queries: fetchingCount,
        mutations: mutatingCount
      });
    } else {
      analytics.track('app_activity_stopped');
    }
  }, [isActive, fetchingCount, mutatingCount]);

  // Prevent page unload during mutations
  useEffect(() => {
    if (mutatingCount > 0) {
      const handleBeforeUnload = (e: BeforeUnloadEvent) => {
        e.preventDefault();
        e.returnValue = 'You have unsaved changes. Are you sure you want to leave?';
        return e.returnValue;
      };

      window.addEventListener('beforeunload', handleBeforeUnload);
      return () => window.removeEventListener('beforeunload', handleBeforeUnload);
    }
  }, [mutatingCount]);

  return (
    <div className="activity-monitor">
      <div className="status-bar">
        {fetchingCount > 0 && (
          <div className="fetching-status">
            <div className="loading-icon" />
            {fetchingCount} loading
          </div>
        )}
        
        {mutatingCount > 0 && (
          <div className="mutating-status">
            <div className="saving-icon" />
            {mutatingCount} saving
          </div>
        )}
        
        {!isActive && (
          <div className="idle-status">
            <div className="idle-icon" />
            All up to date
          </div>
        )}
      </div>
    </div>
  );
}

Feature-Specific Loading States

Monitoring specific application features:

interface FeatureLoadingStates {
  dashboard: number;
  profile: number;
  notifications: number;
  analytics: number;
}

function useFeatureLoadingStates(): FeatureLoadingStates {
  const dashboardLoading = useIsFetching({
    queryKey: ['dashboard'],
    exact: false
  });

  const profileLoading = useIsFetching({
    queryKey: ['profile'],
    exact: false
  }) + useIsMutating({
    mutationKey: ['profile'],
    exact: false
  });

  const notificationsLoading = useIsFetching({
    queryKey: ['notifications'],
    exact: false
  });

  const analyticsLoading = useIsFetching({
    queryKey: ['analytics'],
    exact: false
  });

  return {
    dashboard: dashboardLoading,
    profile: profileLoading,
    notifications: notificationsLoading,
    analytics: analyticsLoading
  };
}

function FeatureStatusPanel() {
  const loadingStates = useFeatureLoadingStates();

  const features = [
    { name: 'Dashboard', count: loadingStates.dashboard },
    { name: 'Profile', count: loadingStates.profile },
    { name: 'Notifications', count: loadingStates.notifications },
    { name: 'Analytics', count: loadingStates.analytics }
  ];

  return (
    <div className="feature-status">
      <h3>Feature Status</h3>
      {features.map(feature => (
        <div key={feature.name} className="feature-item">
          <span>{feature.name}</span>
          <span className={feature.count > 0 ? 'loading' : 'idle'}>
            {feature.count > 0 ? `Loading (${feature.count})` : 'Ready'}
          </span>
        </div>
      ))}
    </div>
  );
}

Performance Monitoring

Tracking query and mutation performance:

function usePerformanceMonitoring() {
  const [metrics, setMetrics] = useState({
    activeQueries: 0,
    activeMutations: 0,
    peakQueries: 0,
    peakMutations: 0,
    totalQueries: 0,
    totalMutations: 0
  });

  const currentQueries = useIsFetching();
  const currentMutations = useIsMutating();

  useEffect(() => {
    setMetrics(prev => ({
      ...prev,
      activeQueries: currentQueries,
      activeMutations: currentMutations,
      peakQueries: Math.max(prev.peakQueries, currentQueries),
      peakMutations: Math.max(prev.peakMutations, currentMutations)
    }));
  }, [currentQueries, currentMutations]);

  // Track when queries/mutations start and complete
  const prevQueries = useRef(0);
  const prevMutations = useRef(0);

  useEffect(() => {
    if (currentQueries > prevQueries.current) {
      setMetrics(prev => ({
        ...prev,
        totalQueries: prev.totalQueries + (currentQueries - prevQueries.current)
      }));
    }
    prevQueries.current = currentQueries;
  }, [currentQueries]);

  useEffect(() => {
    if (currentMutations > prevMutations.current) {
      setMetrics(prev => ({
        ...prev,
        totalMutations: prev.totalMutations + (currentMutations - prevMutations.current)
      }));
    }
    prevMutations.current = currentMutations;
  }, [currentMutations]);

  return metrics;
}

function PerformancePanel() {
  const metrics = usePerformanceMonitoring();

  return (
    <div className="performance-panel">
      <h3>Performance Metrics</h3>
      <div className="metrics-grid">
        <div className="metric">
          <label>Active Queries</label>
          <span>{metrics.activeQueries}</span>
        </div>
        <div className="metric">
          <label>Active Mutations</label>
          <span>{metrics.activeMutations}</span>
        </div>
        <div className="metric">
          <label>Peak Queries</label>
          <span>{metrics.peakQueries}</span>
        </div>
        <div className="metric">
          <label>Peak Mutations</label>
          <span>{metrics.peakMutations}</span>
        </div>
        <div className="metric">
          <label>Total Queries</label>
          <span>{metrics.totalQueries}</span>
        </div>
        <div className="metric">
          <label>Total Mutations</label>
          <span>{metrics.totalMutations}</span>
        </div>
      </div>
    </div>
  );
}

Conditional UI Based on Activity

Adapting UI behavior based on current activity:

function AdaptiveUI({ children }: { children: React.ReactNode }) {
  const isFetching = useIsFetching();
  const isMutating = useIsMutating();

  const isActive = isFetching > 0 || isMutating > 0;
  const isHeavyActivity = isFetching > 3 || isMutating > 2;

  return (
    <div className={`app-container ${isActive ? 'activity-mode' : 'idle-mode'}`}>
      {/* Reduce animations during heavy activity */}
      <style>
        {isHeavyActivity && `
          .app-container * {
            animation-duration: 0.1s !important;
            transition-duration: 0.1s !important;
          }
        `}
      </style>

      {/* Activity overlay */}
      {isActive && (
        <div className="activity-overlay">
          <div className="activity-indicator">
            {isFetching > 0 && <span>Loading data...</span>}
            {isMutating > 0 && <span>Saving changes...</span>}
          </div>
        </div>
      )}

      {/* Disable interactions during critical mutations */}
      <div className={isMutating > 0 ? 'pointer-events-none' : ''}>
        {children}
      </div>
    </div>
  );
}

function NetworkStatusBar() {
  const queryCount = useIsFetching();
  const mutationCount = useIsMutating();

  // Show different indicators based on activity type
  if (mutationCount > 0) {
    return (
      <div className="status-bar saving">
        <div className="icon-saving" />
        <span>Saving {mutationCount} change{mutationCount !== 1 ? 's' : ''}...</span>
      </div>
    );
  }

  if (queryCount > 0) {
    return (
      <div className="status-bar loading">
        <div className="icon-loading" />
        <span>Loading {queryCount} request{queryCount !== 1 ? 's' : ''}...</span>
      </div>
    );
  }

  return (
    <div className="status-bar idle">
      <div className="icon-idle" />
      <span>All data up to date</span>
    </div>
  );
}

Install with Tessl CLI

npx tessl i tessl/npm-react-query

docs

context.md

error-handling.md

index.md

infinite-queries.md

mutations.md

parallel-queries.md

queries.md

ssr.md

status.md

tile.json