import { useState, useEffect, useCallback, useRef } from 'react'; function useFetch(url, options = {}) { const [data, setData] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const abortControllerRef = useRef(null); const { method = 'GET', headers = {}, body = null, skip = false, // Don't auto-fetch on mount ...restOptions } = options; const fetchData = useCallback(async (dynamicBody = null, dynamicOptions = {}) => { setLoading(true); setError(null); // Cancel previous request if (abortControllerRef.current) { abortControllerRef.current.abort(); } abortControllerRef.current = new AbortController(); try { const finalBody = dynamicBody || body; const finalMethod = dynamicOptions.method || method; const config = { method: finalMethod, headers: { 'Content-Type': 'application/json', ...headers, ...dynamicOptions.headers }, signal: abortControllerRef.current.signal, ...restOptions, ...dynamicOptions }; // Add body for POST, PUT, PATCH, DELETE (if needed) if (finalBody && ['POST', 'PUT', 'PATCH', 'DELETE'].includes(finalMethod)) { config.body = typeof finalBody === 'string' ? finalBody : JSON.stringify(finalBody); } const response = await fetch(url, config); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } // Handle empty responses (like 204 No Content) const contentType = response.headers.get('content-type'); let result = null; if (contentType && contentType.includes('application/json')) { result = await response.json(); } else if (response.status !== 204) { result = await response.text(); } setData(result); setError(null); return result; } catch (err) { if (err.name === 'AbortError') { console.log('Fetch aborted'); } else { setError(err.message); throw err; } } finally { setLoading(false); } }, [url, method, body, JSON.stringify(headers), JSON.stringify(restOptions)]); useEffect(() => { if (!skip && method === 'GET') { fetchData(); } return () => { if (abortControllerRef.current) { abortControllerRef.current.abort(); } }; }, [fetchData, skip, method]); const refetch = useCallback(() => { return fetchData(); }, [fetchData]); return { data, loading, error, refetch, execute: fetchData // Alias for manual calls }; } export default useFetch;