arbret/frontend/app/hooks/useAsyncData.ts

66 lines
1.7 KiB
TypeScript
Raw Normal View History

import { useState, useEffect, useCallback } from "react";
/**
* Hook for fetching async data with loading and error states.
* Handles the common pattern of fetching data when component mounts or dependencies change.
*
* @param fetcher - Function that returns a Promise with the data
* @param options - Configuration options
* @returns Object containing data, loading state, error, and refetch function
*/
export function useAsyncData<T>(
fetcher: () => Promise<T>,
options: {
/** Whether the fetch should be enabled (default: true) */
enabled?: boolean;
/** Callback for handling errors */
onError?: (err: unknown) => void;
/** Initial data value (useful for optimistic updates) */
initialData?: T;
} = {}
): {
data: T | null;
isLoading: boolean;
error: string | null;
refetch: () => Promise<void>;
} {
const { enabled = true, onError, initialData } = options;
const [data, setData] = useState<T | null>(initialData ?? null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const fetchData = useCallback(async () => {
if (!enabled) return;
setIsLoading(true);
setError(null);
try {
const result = await fetcher();
setData(result);
} catch (err) {
const errorMessage = err instanceof Error ? err.message : "Failed to load data";
setError(errorMessage);
if (onError) {
onError(err);
} else {
console.error("Failed to fetch data:", err);
}
} finally {
setIsLoading(false);
}
}, [fetcher, enabled, onError]);
useEffect(() => {
fetchData();
}, [fetchData]);
return {
data,
isLoading,
error,
refetch: fetchData,
};
}