import { useState, useCallback } from 'react'; interface LoadingState { data: T | null; isLoading: boolean; error: string | null; isRefreshing: boolean; } interface UseLoadingStateOptions { initialData?: T | null; } interface UseLoadingStateReturn extends LoadingState { setData: (data: T | null) => void; setLoading: (loading: boolean) => void; setError: (error: string | null) => void; setRefreshing: (refreshing: boolean) => void; reset: () => void; execute: ( asyncFn: () => Promise, options?: { refresh?: boolean; transform?: (result: R) => T } ) => Promise; } /** * Custom hook for managing loading states * Provides consistent loading, error, and data state management */ export function useLoadingState( options: UseLoadingStateOptions = {} ): UseLoadingStateReturn { const { initialData = null } = options; const [state, setState] = useState>({ data: initialData, isLoading: false, error: null, isRefreshing: false, }); const setData = useCallback((data: T | null) => { setState((prev) => ({ ...prev, data, error: null })); }, []); const setLoading = useCallback((isLoading: boolean) => { setState((prev) => ({ ...prev, isLoading })); }, []); const setError = useCallback((error: string | null) => { setState((prev) => ({ ...prev, error, isLoading: false, isRefreshing: false })); }, []); const setRefreshing = useCallback((isRefreshing: boolean) => { setState((prev) => ({ ...prev, isRefreshing })); }, []); const reset = useCallback(() => { setState({ data: initialData, isLoading: false, error: null, isRefreshing: false, }); }, [initialData]); /** * Execute an async function with automatic loading state management * @param asyncFn - The async function to execute * @param options.refresh - If true, sets isRefreshing instead of isLoading * @param options.transform - Optional function to transform the result before setting data */ const execute = useCallback( async ( asyncFn: () => Promise, execOptions?: { refresh?: boolean; transform?: (result: R) => T } ): Promise => { const { refresh = false, transform } = execOptions || {}; try { if (refresh) { setState((prev) => ({ ...prev, isRefreshing: true, error: null })); } else { setState((prev) => ({ ...prev, isLoading: true, error: null })); } const result = await asyncFn(); // Transform and set data if transform function provided if (transform) { const transformedData = transform(result); setState((prev) => ({ ...prev, data: transformedData, isLoading: false, isRefreshing: false, })); } else { // If no transform, try to set result as data (if types match) setState((prev) => ({ ...prev, data: result as unknown as T, isLoading: false, isRefreshing: false, })); } return result; } catch (err) { const errorMessage = err instanceof Error ? err.message : 'An error occurred'; setState((prev) => ({ ...prev, error: errorMessage, isLoading: false, isRefreshing: false, })); return undefined; } }, [] ); return { ...state, setData, setLoading, setError, setRefreshing, reset, execute, }; } /** * Simple boolean loading state hook * For cases where you just need a loading flag */ export function useSimpleLoading(initialState = false) { const [isLoading, setIsLoading] = useState(initialState); const startLoading = useCallback(() => setIsLoading(true), []); const stopLoading = useCallback(() => setIsLoading(false), []); const toggleLoading = useCallback(() => setIsLoading((prev) => !prev), []); const withLoading = useCallback( async (asyncFn: () => Promise): Promise => { setIsLoading(true); try { return await asyncFn(); } finally { setIsLoading(false); } }, [] ); return { isLoading, setIsLoading, startLoading, stopLoading, toggleLoading, withLoading, }; } /** * Hook for managing multiple loading states by key * Useful for list items that can have individual loading states */ export function useMultipleLoadingStates() { const [loadingStates, setLoadingStates] = useState>({}); const setLoading = useCallback((key: string, loading: boolean) => { setLoadingStates((prev) => ({ ...prev, [key]: loading })); }, []); const isLoading = useCallback( (key: string) => loadingStates[key] || false, [loadingStates] ); const anyLoading = Object.values(loadingStates).some(Boolean); const clearAll = useCallback(() => { setLoadingStates({}); }, []); return { loadingStates, setLoading, isLoading, anyLoading, clearAll, }; }