All files / src/hooks useServiceData.ts

100% Statements 11/11
100% Branches 3/3
100% Functions 4/4
100% Lines 10/10

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 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124                                                                                                                                                                                        68x           68x 35x   35x 35x 35x   8x               68x 29x       68x          
import { useState, useEffect, DependencyList } from 'react';
 
/**
 * Service data state
 * 
 * @template T - Type of data returned by the service
 */
export interface ServiceDataState<T> {
  /** Fetched data, null if not yet loaded or if an error occurred */
  data: T | null;
  /** Loading state - true while data is being fetched */
  loading: boolean;
  /** Error if fetch failed, null otherwise */
  error: Error | null;
  /** Function to manually trigger a refetch of the data */
  refetch: () => void;
}
 
/**
 * Custom hook for fetching service data with loading and error states
 * 
 * ## Business Perspective
 * 
 * Standardizes data fetching across all widgets, ensuring consistent loading
 * states and error handling. This improves user experience by providing
 * predictable feedback during data operations and simplifies maintenance
 * by centralizing error handling logic. 🔄
 * 
 * ## Technical Perspective
 * 
 * Extracts the common loading/error/data pattern found in 6+ widgets,
 * reducing code duplication and ensuring consistent error handling. Uses
 * React hooks best practices with proper dependency management.
 * 
 * Note: This hook currently supports synchronous fetch functions only.
 * The services in this application return data synchronously, so this
 * design choice simplifies the implementation and testing. For async
 * operations, the hook can be extended in the future if needed.
 * 
 * @template T - Type of data returned by the fetch function
 * @param fetchFn - Synchronous function to fetch data
 * @param deps - Dependency array for useEffect (when to re-fetch)
 * @returns Service data state with loading, error, data, and refetch function
 * 
 * @example
 * ```tsx
 * // Basic usage with service call
 * const { data, loading, error, refetch } = useServiceData(
 *   () => securityMetricsService.getMetrics(level),
 *   [level]
 * );
 * 
 * if (loading) return <LoadingSpinner />;
 * if (error) return <ErrorMessage error={error} retry={refetch} />;
 * if (!data) return <NoDataMessage />;
 * 
 * return <DataDisplay data={data} />;
 * ```
 * 
 * @example
 * ```tsx
 * // Usage with multiple dependencies
 * const { data, loading, error } = useServiceData(
 *   () => getSecurityMetrics(
 *     levels.availability,
 *     levels.integrity,
 *     levels.confidentiality
 *   ),
 *   [levels.availability, levels.integrity, levels.confidentiality]
 * );
 * ```
 * 
 * @example
 * ```tsx
 * // Manual refetch on user action
 * const { data, refetch } = useServiceData(
 *   () => complianceService.getStatus(level),
 *   [level]
 * );
 * 
 * return (
 *   <div>
 *     <DataDisplay data={data} />
 *     <button onClick={refetch}>Refresh</button>
 *   </div>
 * );
 * ```
 */
export function useServiceData<T>(
  fetchFn: () => T,
  deps: DependencyList = []
): ServiceDataState<T> {
  const [state, setState] = useState<Omit<ServiceDataState<T>, 'refetch'>>({
    data: null,
    loading: true,
    error: null,
  });
 
  const fetchData = (): void => {
    setState(prev => ({ ...prev, loading: true, error: null }));
    
    try {
      const data = fetchFn();
      setState({ data, loading: false, error: null });
    } catch (error) {
      setState({
        data: null,
        loading: false,
        error: error instanceof Error ? error : new Error('Unknown error occurred'),
      });
    }
  };
 
  useEffect(() => {
    fetchData();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, deps);
 
  return {
    ...state,
    refetch: fetchData,
  };
}