import { useState, useCallback, useRef, useEffect } from 'react' import { ApiError } from '~/types/task' interface UseApiState { data: T | null loading: boolean error: string | null } interface UseApiActions { execute: (...args: unknown[]) => Promise reset: () => void clearError: () => void } interface UseApiOptions { immediate?: boolean onSuccess?: (data: unknown) => void onError?: (error: string) => void } export function useApi( apiFunction: (...args: unknown[]) => Promise, options: UseApiOptions = {} ): UseApiState & UseApiActions { const { immediate = false, onSuccess, onError } = options const [state, setState] = useState>({ data: null, loading: false, error: null, }) const mountedRef = useRef(true) const abortControllerRef = useRef(null) useEffect(() => { return () => { mountedRef.current = false if (abortControllerRef.current) { abortControllerRef.current.abort() } } }, []) const reset = useCallback(() => { setState({ data: null, loading: false, error: null, }) }, []) const clearError = useCallback(() => { setState(prev => ({ ...prev, error: null })) }, []) const execute = useCallback( async (...args: unknown[]): Promise => { // Cancel previous request if still pending if (abortControllerRef.current) { abortControllerRef.current.abort() } abortControllerRef.current = new AbortController() setState(prev => ({ ...prev, loading: true, error: null })) try { const result = await apiFunction(...args) if (!mountedRef.current) return null setState(prev => ({ ...prev, data: result, loading: false, })) if (onSuccess) { onSuccess(result) } return result } catch (error) { if (!mountedRef.current) return null const apiError = error as ApiError const errorMessage = apiError.message || 'An unknown error occurred' setState(prev => ({ ...prev, loading: false, error: errorMessage, })) if (onError) { onError(errorMessage) } return null } finally { abortControllerRef.current = null } }, [apiFunction, onSuccess, onError] ) // Execute immediately if requested useEffect(() => { if (immediate) { execute() } }, [immediate, execute]) return { ...state, execute, reset, clearError, } } // Utility hook for handling form submissions export function useApiForm( submitFunction: (data: unknown) => Promise, options: UseApiOptions & { resetOnSuccess?: boolean } = {} ) { const { resetOnSuccess = false, ...apiOptions } = options const api = useApi(submitFunction, apiOptions) const handleSubmit = useCallback( async (data: unknown) => { const result = await api.execute(data) if (result && resetOnSuccess) { api.reset() } return result }, [api, resetOnSuccess] ) return { ...api, handleSubmit, } } // Utility hook for data caching and synchronization export function useApiCache( key: string, fetchFunction: () => Promise, options: { cacheTime?: number; staleTime?: number } = {} ) { const { cacheTime = 5 * 60 * 1000, staleTime = 30 * 1000 } = options // 5min cache, 30s stale const [cacheData, setCacheData] = useState<{ data: T | null timestamp: number } | null>(null) const api = useApi(fetchFunction) const getCachedData = useCallback((): T | null => { if (!cacheData) return null const now = Date.now() const age = now - cacheData.timestamp if (age > cacheTime) { setCacheData(null) return null } return cacheData.data }, [cacheData, cacheTime]) const isStale = useCallback((): boolean => { if (!cacheData) return true const now = Date.now() const age = now - cacheData.timestamp return age > staleTime }, [cacheData, staleTime]) const fetchData = useCallback( async (force = false): Promise => { // Return cached data if fresh and not forced if (!force && !isStale()) { const cached = getCachedData() if (cached) return cached } const result = await api.execute() if (result) { setCacheData({ data: result, timestamp: Date.now(), }) } return result }, [api, isStale, getCachedData] ) const clearCache = useCallback(() => { setCacheData(null) api.reset() }, [api]) return { data: api.data || getCachedData(), loading: api.loading, error: api.error, fetchData, clearCache, get isStale() { return isStale() }, } }