UNPKG

use-fetch-react-xhr

Version:

A custom hook that's alternative to axios to simplify making api calls for React Application and it also lets you cancel request while it's in progress

305 lines (277 loc) 7.13 kB
import { useState, useEffect } from "react"; export type ApiParams = { url: string; payload?: any; type?: "GET" | "POST" | "PUT" | "DELETE"; headers?: Record<string, string>; autoLoad?: boolean; callOnMount?: boolean; }; export type ApiCallResult = { status: "idle" | "loading" | "success" | "error" | "cancelled"; data?: any; error?: any; isLoading: boolean; isSuccess: boolean; isError: boolean; isCancelled: boolean; request: XMLHttpRequest | null; }; export type UseFetchResponse = { isLoading: boolean; isSuccess: boolean; isError: boolean; data: any; error: APIError; isCancelled: boolean; load: () => void; cancel: () => void; request: XMLHttpRequest | null; }; type ApiCallOptions = { onSuccess?: (data: any) => void; onError?: (error: any) => void; onCancel?: () => void; }; type StartFunction = ( url: string, type?: string, payload?: any, headers?: Record<string, string> ) => void; type RequestParams = { url: string; type?: string; payload?: any; headers?: Record<string, string>; }; const MIN_REQUEST_DELAY = 50; export const createApiCallFunction = () => { let listener: any = null; let request: XMLHttpRequest | any; let isCancelled = false; let lastRequestParams: RequestParams | undefined; let lastRequestTime = 0; let requestStatus = { status: "idle", isLoading: false, isSuccess: false, isError: false, isCancelled: false, }; const notifyListener = (result: ApiCallResult) => { if (result) { requestStatus = result; } if (listener) { listener(requestStatus); } }; const start: StartFunction = ( url, type = "GET", payload, headers = { "content-type": "application/json" } ) => { const now = Date.now(); const requestParams: RequestParams = { url, type, payload, headers }; if ( request && now - lastRequestTime < MIN_REQUEST_DELAY && isEqual(requestParams, lastRequestParams) ) { // Ignore this request return; } lastRequestParams = requestParams; lastRequestTime = now; isCancelled = false; request = new XMLHttpRequest(); request.open(type, url, true); if (headers) { for (const header in headers) { request.setRequestHeader(header, headers[header]); } } request.onreadystatechange = () => { if (isCancelled) { return; } if (request.readyState === 4) { try { if (request.status >= 200 && request.status < 300) { const response = JSON.parse(request.responseText); // throw Error('new error'); notifyListener({ status: "success", data: response, isLoading: false, isSuccess: true, isError: false, request, error: null, isCancelled: false, }); } else { let data = ""; if ( request .getResponseHeader("Content-Type") .includes("application/json") ) { data = JSON.parse(request.responseText); } const error = { status: request.status, statusText: request.statusText, data: data, message: "Error Loading Data!", }; notifyListener({ status: "error", error, request, isLoading: false, isSuccess: false, isError: true, isCancelled: false, }); } } catch (error) { notifyListener({ status: "error", error: { status: request.status, statusText: request.statusText, data: {}, message: "error while parsing response from server" + error, }, request, isLoading: false, isSuccess: false, isError: true, isCancelled: false, }); } } }; // setTimeout(() => { request.send( headers["content-type"] === "application/json" ? JSON.stringify(payload) : payload ); notifyListener({ status: "loading", isLoading: true, isSuccess: false, isError: false, isCancelled: false, request, }); // }, MIN_REQUEST_DELAY); }; const cancel = () => { if (request && requestStatus.isLoading) { request.abort(); isCancelled = true; notifyListener({ status: "cancelled", isLoading: false, isSuccess: false, isError: false, isCancelled: true, request, }); } }; const removeListener = () => { listener = null; }; const onChange = (newListener: any) => { listener = newListener; }; return { onChange, start, cancel, removeListener, }; }; function isEqual(a: any, b: any) { return JSON.stringify(a) === JSON.stringify(b); } export type APIError = { status: number; statusText: string; message: any; data: any; }; export const useFetch = ({ url, payload, headers, type, autoLoad = false, callOnMount = false, }: ApiParams): UseFetchResponse => { const [result, setResult] = useState<ApiCallResult>({ status: "idle", data: null, error: null, isLoading: false, isSuccess: false, isError: false, isCancelled: false, request: null, }); const [apiCall, setAPICall] = useState<any>(null); const [makeInitialCall, setMakeInitialCall] = useState(callOnMount); useEffect(() => { setAPICall(createApiCallFunction()); }, []); useEffect(() => { if (apiCall) { const onChange = (apiResult: ApiCallResult) => setResult(apiResult); apiCall.onChange(onChange); if (makeInitialCall) { load(); } return () => { apiCall?.removeListener(onChange); cancel(); }; } }, [apiCall]); useEffect(() => { if (apiCall && autoLoad) { cancel(); load(); return () => { result.isLoading && apiCall.cancel(); }; } }, [url, payload, headers, type]); const load = () => { if (apiCall) { cancel(); apiCall.start(url, type, payload, headers); } else { setMakeInitialCall(true); } }; const cancel = () => { result.isLoading && apiCall?.cancel(); }; return { isLoading: result.isLoading || false, isSuccess: result.isSuccess || false, isError: result.isError || false, data: result.data || null, error: result.error || "", isCancelled: result.isCancelled || false, load, cancel, request: result.request, }; };