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