/** * Оптимизированный хук для загрузки данных с кешированием */ import { useState, useEffect, useCallback, useRef } from 'react'; import { cache } from '@/lib/cache'; import apiClient from '@/lib/api-client'; interface UseOptimizedFetchOptions { url: string; cacheKey?: string; /** Не используется пока кэш отключён глобально. Оставлен для последующего включения по месту. */ cacheTTL?: number; enabled?: boolean; onSuccess?: (data: T) => void; onError?: (error: Error) => void; } interface UseOptimizedFetchResult { data: T | null; loading: boolean; error: Error | null; refetch: () => Promise; clearCache: () => void; mutate: (updater: T | ((prev: T | null) => T | null)) => void; } export function useOptimizedFetch({ url, cacheKey, cacheTTL, enabled = true, onSuccess, onError, }: UseOptimizedFetchOptions): UseOptimizedFetchResult { const key = cacheKey || url; const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const abortControllerRef = useRef(null); const fetchData = useCallback(async () => { // Отменяем предыдущий запрос if (abortControllerRef.current) { abortControllerRef.current.abort(); } abortControllerRef.current = new AbortController(); try { setLoading(true); setError(null); // Кэш отключён везде до анализа на Production (см. lib/cache.ts, api-client.ts) const response = await apiClient.get(url, { signal: abortControllerRef.current.signal, cache: false, }); const newData = response.data; setData(newData); onSuccess?.(newData); } catch (err: any) { // Не показываем ошибку при отмене запроса (навигация, размонтирование, refetch) const isCanceled = err?.name === 'AbortError' || err?.name === 'CanceledError' || err?.code === 'ERR_CANCELED' || err?.message === 'canceled'; if (isCanceled) { return; } const error = err instanceof Error ? err : new Error(err?.message || 'Unknown error'); setError(error); onError?.(error); } finally { if (abortControllerRef.current && !abortControllerRef.current.signal.aborted) { setLoading(false); } } }, [url, onSuccess, onError]); useEffect(() => { if (!enabled) { setLoading(false); return; } fetchData(); return () => { if (abortControllerRef.current) { abortControllerRef.current.abort(); } }; }, [enabled, fetchData]); const clearCache = useCallback(() => { cache.delete(key); setData(null); }, [key]); // Локальное обновление данных без перезагрузки (кэш отключён) const mutate = useCallback((updater: T | ((prev: T | null) => T | null)) => { setData((prev) => { return typeof updater === 'function' ? (updater as (prev: T | null) => T | null)(prev) : updater; }); }, []); return { data, loading, error, refetch: fetchData, clearCache, mutate, }; }