use-prefetch-thread.ts4.5 KBView on GitHub
import { ConversationIdState } from '@/modules/conversations/constants';
import { useQueryClient } from '@tanstack/react-query';
import { useTRPC } from '@/providers/query-provider';
import { useCallback } from 'react';

/**
 * Check whether the current network connection is fast enough to justify prefetching.
 * Returns true when the Network Information API is unavailable (desktop browsers)
 * or when the connection is reasonably fast. Returns false on 2g / slow-2g or
 * when the user has opted into data-saver mode.
 */
export function shouldPrefetch(): boolean {
  const conn = (navigator as any).connection;
  if (!conn) return true;
  if (conn.saveData) return false;
  if (conn.effectiveType === '2g' || conn.effectiveType === 'slow-2g') return false;
  return true;
}

/**
 * Hook for prefetching mail.get() and conversation data to improve thread loading performance.
 * Uses React Query's prefetchQuery to cache data before it's needed.
 *
 * React Query handles deduplication and cache checking automatically, so we can
 * safely call prefetch multiple times without creating redundant network requests.
 *
 * Common use cases:
 * - Prefetch on hover over thread list items
 * - Prefetch on thread selection
 * - Prefetch related threads from conversation data
 * - Prefetch all threads when list loads
 */
export function usePrefetchThread() {
  const queryClient = useQueryClient();
  const trpc = useTRPC();

  /**
   * Prefetch a single thread's data.
   * React Query will automatically deduplicate requests and use cache if available.
   * Prefetches the full thread query so it's ready when the thread is opened.
   * @param threadId - The thread ID to prefetch
   */
  const prefetchThread = useCallback(
    (threadId: string | null | undefined) => {
      if (!threadId) return;

      queryClient.prefetchQuery(trpc.mail.get.queryOptions({ id: threadId }));
    },
    [queryClient, trpc],
  );

  /**
   * Prefetch multiple threads in parallel.
   * React Query will automatically deduplicate requests and use cache if available.
   * @param threadIds - Array of thread IDs to prefetch
   */
  const prefetchThreads = useCallback(
    (threadIds: (string | null | undefined)[]) => {
      const validIds = threadIds.filter((id): id is string => !!id);

      if (validIds.length === 0) return;

      // Prefetch all threads in parallel - React Query handles deduplication
      // Don't pass staleTime - let each query use its own staleness criteria
      validIds.forEach((threadId) => {
        queryClient.prefetchQuery({
          ...trpc.mail.get.queryOptions({ id: threadId }),
          staleTime: 3 * 60 * 1000,
        });
      });
    },
    [queryClient, trpc],
  );

  /**
   * Check if conversationId is a special loading/failed state that requires loadConversation.
   * Matches the logic in ConversationDataSync.tsx
   */
  const needsLoading = (id: string | null | undefined): boolean => {
    if (!id) return true; // No conversationId means we need to load
    return (
      id === ConversationIdState.LOADING_CONVERSATION ||
      id === ConversationIdState.FAILED_CREATING_CONVERSATION
    );
  };

  /**
   * Prefetch conversation data for a thread.
   * Uses the same get/load differentiation as ConversationDataSync.tsx:
   * - If conversationId exists and is NOT a special state → use getConversation (faster, direct lookup)
   * - If conversationId is missing or is a special state → use loadConversation (finds/creates by threadId)
   * @param threadId - The thread ID
   * @param conversationId - Optional conversation ID if already known
   */
  const prefetchConversation = useCallback(
    (threadId: string | null | undefined, conversationId?: string | null) => {
      if (!threadId && !conversationId) return;

      const shouldLoad = needsLoading(conversationId) && threadId;

      if (!shouldLoad && conversationId) {
        // Have a valid conversationId that's not a special state - use getConversation
        queryClient.prefetchQuery({
          ...trpc.crm.getConversation.queryOptions({ id: conversationId }),
          staleTime: 2 * 60 * 1000,
        });
      } else if (shouldLoad && threadId) {
        // Need to load by threadId - use loadConversation
        queryClient.prefetchQuery({
          ...trpc.crm.loadConversation.queryOptions({
            threadId,
            conversationId: conversationId || undefined,
          }),
          staleTime: 5 * 60 * 1000,
        });
      }
    },
    [queryClient, trpc],
  );

  return {
    prefetchThread,
    prefetchThreads,
    prefetchConversation,
  };
}