UNPKG

use-fetch-with-callbacks

Version:

A powerful React hook for HTTP requests with comprehensive callback support, request chaining, and TypeScript integration

412 lines (411 loc) 17.4 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { useState, useCallback, useRef, useEffect, useMemo } from 'react'; /** * A React hook that provides fetch functionality with callback support for success, error, and loading states * * @template T - The type of data expected from the API response * @param endpoint - The API endpoint path to make requests to * @param options - Optional configuration including base URL and headers * @returns An object containing response data, loading state, error state, and request methods * * @example * Basic usage: * ```typescript * const { response, loading, error, fetchData } = useFetchWithCallbacks<User>('/users/1', { * baseUrl: 'https://api.example.com', * headers: { 'Authorization': 'Bearer token' } * }); * * // Fetch data with callbacks * fetchData( * (data) => console.log('Success:', data), * (error) => console.error('Error:', error), * (loading) => console.log('Loading:', loading) * ); * ``` * * @example * Chaining operations: * ```typescript * const { chain } = useFetchWithCallbacks<User>('/users/1', { * baseUrl: 'https://api.example.com' * }); * * // Chain multiple operations * chain() * .fetch((data) => console.log('Fetched:', data)) * .post({ name: 'John' }, (data) => console.log('Posted:', data)) * .put({ name: 'Jane' }, (data) => console.log('Updated:', data)) * .then((finalData) => console.log('All operations completed:', finalData)) * .catch((error) => console.error('Chain failed:', error)) * .finally(() => console.log('Chain finished')) * .execute(); * ``` */ const useFetchWithCallbacks = (endpoint, options) => { var _a, _b; const [response, setResponse] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [requestCompleted, setRequestCompleted] = useState(false); const abortControllerRef = useRef(null); const baseUrl = (_a = options === null || options === void 0 ? void 0 : options.baseUrl) !== null && _a !== void 0 ? _a : ''; const timeout = (_b = options === null || options === void 0 ? void 0 : options.timeout) !== null && _b !== void 0 ? _b : 10000; const headers = useMemo(() => (Object.assign({ 'Content-Type': 'application/json' }, options === null || options === void 0 ? void 0 : options.headers)), [options === null || options === void 0 ? void 0 : options.headers]); const setLoadingState = (isLoading, onLoading) => { setLoading(isLoading); if (onLoading) { onLoading(isLoading); } }; const executeMultipleRequest = useCallback((fetchFn, onSuccess, onError, onLoading) => __awaiter(void 0, void 0, void 0, function* () { // Cancel any ongoing request if (abortControllerRef.current) { abortControllerRef.current.abort(); } // Create new abort controller const abortController = new AbortController(); abortControllerRef.current = abortController; setLoadingState(true, onLoading); setError(null); setRequestCompleted(false); try { const data = yield fetchFn(abortController.signal); // Check if request was aborted if (abortController.signal.aborted) { return; } setResponse(data); setRequestCompleted(true); if (onSuccess) { onSuccess(data); } } catch (err) { // Don't set error if request was aborted if (abortController.signal.aborted) { return; } const errorObj = err instanceof Error ? err : new Error(String(err)); setError(errorObj); if (onError) { onError(errorObj); } } finally { if (!abortController.signal.aborted) { setLoadingState(false, onLoading); } } }), [timeout]); const executeRequest = useCallback((fetchFn, onSuccess, onError, onLoading) => __awaiter(void 0, void 0, void 0, function* () { // Cancel any ongoing request if (abortControllerRef.current) { abortControllerRef.current.abort(); } // Create new abort controller const abortController = new AbortController(); abortControllerRef.current = abortController; setLoadingState(true, onLoading); setError(null); setRequestCompleted(false); try { const data = yield fetchFn(abortController.signal); // Check if request was aborted if (abortController.signal.aborted) { return; } setResponse(data); setRequestCompleted(true); if (onSuccess) { onSuccess(data); } } catch (err) { // Don't set error if request was aborted if (abortController.signal.aborted) { return; } const errorObj = err instanceof Error ? err : new Error(String(err)); setError(errorObj); if (onError) { onError(errorObj); } } finally { if (!abortController.signal.aborted) { setLoadingState(false, onLoading); } } }), []); // Cleanup effect to abort requests when component unmounts useEffect(() => { return () => { if (abortControllerRef.current) { abortControllerRef.current.abort(); } }; }, []); const fetchData = useCallback((onSuccess, onError, onLoading) => executeRequest((abortSignal) => __awaiter(void 0, void 0, void 0, function* () { const url = `${baseUrl}${endpoint}`; const res = yield Promise.race([ fetch(url, { method: 'GET', headers, signal: abortSignal }), new Promise((_, reject) => setTimeout(() => reject(new Error('Request timeout')), timeout)), ]); if (!res.ok) { const errorText = yield res.text(); let errorMessage = 'An error occurred'; try { const errorJson = JSON.parse(errorText); errorMessage = errorJson.message || errorMessage; } catch (_a) { errorMessage = errorText || errorMessage; } throw new Error(errorMessage); } const json = yield res.json(); return json; }), onSuccess, onError, onLoading), [baseUrl, endpoint, headers, executeRequest, timeout]); const postData = useCallback((data, onSuccess, onError, onLoading) => executeRequest((abortSignal) => __awaiter(void 0, void 0, void 0, function* () { const url = `${baseUrl}${endpoint}`; const res = yield Promise.race([ fetch(url, { method: 'POST', headers, body: JSON.stringify(data), signal: abortSignal, }), new Promise((_, reject) => setTimeout(() => reject(new Error('Request timeout')), timeout)), ]); if (!res.ok) { const errorText = yield res.text(); let errorMessage = 'An error occurred'; try { const errorJson = JSON.parse(errorText); errorMessage = errorJson.message || errorMessage; } catch (_a) { errorMessage = errorText || errorMessage; } throw new Error(errorMessage); } const json = yield res.json(); return json; }), onSuccess, onError, onLoading), [baseUrl, endpoint, headers, executeRequest, timeout]); const putData = useCallback((data, onSuccess, onError, onLoading) => executeRequest((abortSignal) => __awaiter(void 0, void 0, void 0, function* () { const url = `${baseUrl}${endpoint}`; const res = yield Promise.race([ fetch(url, { method: 'PUT', headers, body: JSON.stringify(data), signal: abortSignal, }), new Promise((_, reject) => setTimeout(() => reject(new Error('Request timeout')), timeout)), ]); if (!res.ok) { const errorText = yield res.text(); let errorMessage = 'An error occurred'; try { const errorJson = JSON.parse(errorText); errorMessage = errorJson.message || errorMessage; } catch (_a) { errorMessage = errorText || errorMessage; } throw new Error(errorMessage); } const json = yield res.json(); return json; }), onSuccess, onError, onLoading), [baseUrl, endpoint, headers, executeRequest, timeout]); const deleteData = useCallback((onSuccess, onError, onLoading) => executeRequest((abortSignal) => __awaiter(void 0, void 0, void 0, function* () { const url = `${baseUrl}${endpoint}`; const res = yield Promise.race([ fetch(url, { method: 'DELETE', headers, signal: abortSignal, }), new Promise((_, reject) => setTimeout(() => reject(new Error('Request timeout')), timeout)), ]); if (!res.ok) { const errorText = yield res.text(); let errorMessage = 'An error occurred'; try { const errorJson = JSON.parse(errorText); errorMessage = errorJson.message || errorMessage; } catch (_a) { errorMessage = errorText || errorMessage; } throw new Error(errorMessage); } const json = yield res.json(); return json; }), onSuccess, onError, onLoading), [baseUrl, endpoint, headers, executeRequest, timeout]); const patchData = useCallback((data, onSuccess, onError, onLoading) => executeRequest((abortSignal) => __awaiter(void 0, void 0, void 0, function* () { const url = `${baseUrl}${endpoint}`; const res = yield Promise.race([ fetch(url, { method: 'PATCH', headers, body: JSON.stringify(data), signal: abortSignal, }), new Promise((_, reject) => setTimeout(() => reject(new Error('Request timeout')), timeout)), ]); if (!res.ok) { const errorText = yield res.text(); let errorMessage = 'An error occurred'; try { const errorJson = JSON.parse(errorText); errorMessage = errorJson.message || errorMessage; } catch (_a) { errorMessage = errorText || errorMessage; } throw new Error(errorMessage); } const json = yield res.json(); return json; }), onSuccess, onError, onLoading), [baseUrl, endpoint, headers, executeRequest, timeout]); const fetchMultipleData = useCallback((endpoints, onSuccess, onError, onLoading) => executeMultipleRequest((abortSignal) => __awaiter(void 0, void 0, void 0, function* () { const promises = endpoints.map((ep) => __awaiter(void 0, void 0, void 0, function* () { const url = `${baseUrl}${ep}`; const res = yield Promise.race([ fetch(url, { method: 'GET', headers, signal: abortSignal }), new Promise((_, reject) => setTimeout(() => reject(new Error('Request timeout')), timeout)), ]); if (!res.ok) { const errorText = yield res.text(); let errorMessage = 'An error occurred'; try { const errorJson = JSON.parse(errorText); errorMessage = errorJson.message || errorMessage; } catch (_a) { errorMessage = errorText || errorMessage; } throw new Error(errorMessage); } return res.json(); })); return Promise.all(promises); }), onSuccess, onError, onLoading), [baseUrl, headers, executeMultipleRequest, timeout]); const chain = useCallback(() => { const operations = []; let finalSuccessCallback; let finalErrorCallback; let finallyCallback; const chainableRequest = { fetch: (onSuccess, onError, onLoading) => { operations.push({ type: 'GET', onSuccess, onError, onLoading }); return chainableRequest; }, post: (data, onSuccess, onError, onLoading) => { operations.push({ type: 'POST', data, onSuccess, onError, onLoading }); return chainableRequest; }, put: (data, onSuccess, onError, onLoading) => { operations.push({ type: 'PUT', data, onSuccess, onError, onLoading }); return chainableRequest; }, delete: (onSuccess, onError, onLoading) => { operations.push({ type: 'DELETE', onSuccess, onError, onLoading }); return chainableRequest; }, patch: (data, onSuccess, onError, onLoading) => { operations.push({ type: 'PATCH', data, onSuccess, onError, onLoading }); return chainableRequest; }, then: callback => { finalSuccessCallback = callback; return chainableRequest; }, catch: callback => { finalErrorCallback = callback; return chainableRequest; }, finally: callback => { finallyCallback = callback; return chainableRequest; }, execute: () => __awaiter(void 0, void 0, void 0, function* () { let lastResult = null; try { for (const operation of operations) { yield new Promise((resolve, reject) => { const onSuccess = (data) => { lastResult = data; if (operation.onSuccess) { operation.onSuccess(data); } resolve(); }; const onError = (error) => { if (operation.onError) { operation.onError(error); } reject(error); }; switch (operation.type) { case 'GET': fetchData(onSuccess, onError, operation.onLoading); break; case 'POST': postData(operation.data, onSuccess, onError, operation.onLoading); break; case 'PUT': putData(operation.data, onSuccess, onError, operation.onLoading); break; case 'DELETE': deleteData(onSuccess, onError, operation.onLoading); break; case 'PATCH': patchData(operation.data, onSuccess, onError, operation.onLoading); break; } }); } if (finalSuccessCallback && lastResult) { finalSuccessCallback(lastResult); } } catch (error) { if (finalErrorCallback) { finalErrorCallback(error); } } finally { if (finallyCallback) { finallyCallback(); } } }), }; return chainableRequest; }, [fetchData, postData, putData, deleteData, patchData]); return { response, loading, error, requestCompleted, fetchData, postData, putData, deleteData, patchData, fetchMultipleData, chain, }; }; export default useFetchWithCallbacks;