All files / src/common/hooks useSourceSearch.ts

88.52% Statements 54/61
77.77% Branches 7/9
100% Functions 1/1
88.52% Lines 54/61

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 951x     1x                               1x 76x   76x 76x 76x 76x     76x 1x           1x 1x   1x 1x 1x 1x 1x 1x 1x 1x 1x   1x   1x 1x       1x 1x 1x 76x     76x 5x     5x 3x 3x 3x 3x     2x 1x 2x   2x 76x   76x 1x 1x 1x 76x   76x 76x 76x 76x 76x 76x 76x 76x 76x  
import { newsletterSourceService } from '@common/services/newsletterSource/NewsletterSourceService';
import type { NewsletterSource } from '@common/types';
import type { NewsletterSourceQueryParams } from '@common/types/api';
import { useCallback, useState } from 'react';
 
interface UseSourceSearchOptions {
  debounceMs?: number;
  minQueryLength?: number;
}
 
interface UseSourceSearchReturn {
  searchResults: NewsletterSource[];
  isSearching: boolean;
  searchQuery: string;
  setSearchQuery: (query: string) => void;
  clearSearch: () => void;
  searchError: string | null;
}
 
export const useSourceSearch = (options: UseSourceSearchOptions = {}): UseSourceSearchReturn => {
  const { debounceMs = 300, minQueryLength = 2 } = options;
 
  const [searchResults, setSearchResults] = useState<NewsletterSource[]>([]);
  const [isSearching, setIsSearching] = useState(false);
  const [searchQuery, setSearchQueryState] = useState('');
  const [searchError, setSearchError] = useState<string | null>(null);
 
  // Debounced search function
  const performSearch = useCallback(async (query: string) => {
    if (query.length < minQueryLength) {
      setSearchResults([]);
      setSearchError(null);
      return;
    }
 
    setIsSearching(true);
    setSearchError(null);
 
    try {
      const searchParams: NewsletterSourceQueryParams = {
        search: query.trim(),
        excludeArchived: false,
        limit: 50, // Limit search results for performance
        orderBy: 'name',
        orderDirection: 'asc',
        includeCount: false, // No need for counts in search results
      };
 
      const result = await newsletterSourceService.getSources(searchParams);
 
      setSearchResults(result.data);
    } catch (error) {
      console.error('Source search error:', error);
      setSearchError(error instanceof Error ? error.message : 'Search failed');
      setSearchResults([]);
    } finally {
      setIsSearching(false);
    }
  }, [minQueryLength]);
 
  // Debounced search
  const setSearchQuery = useCallback((query: string) => {
    setSearchQueryState(query);
 
    // Clear results immediately if query is too short
    if (query.length < minQueryLength) {
      setSearchResults([]);
      setSearchError(null);
      return;
    }
 
    // Debounce the actual search
    const timeoutId = setTimeout(() => {
      performSearch(query);
    }, debounceMs);
 
    return () => clearTimeout(timeoutId);
  }, [performSearch, debounceMs, minQueryLength]);
 
  const clearSearch = useCallback(() => {
    setSearchQueryState('');
    setSearchResults([]);
    setSearchError(null);
  }, []);
 
  return {
    searchResults,
    isSearching,
    searchQuery,
    setSearchQuery,
    clearSearch,
    searchError,
  };
};