Custom react hook for API call:- useAsync
Series: Custom React Hooks
Episodes: (9/10)
- Custom React Hook for Full Screen Mode
- Custom React Hook for Queue
- Custom React Hook for Array
- Custom React Hook for Event Listening
- Custom React Hook for using setInterval
- Custom React Hook for using setTimeout
- Custom react hook for client idle detection:- useIdle
- Custom react hook for checking client internet connection:- useOnlineStatus
- Custom react hook for API call:- useAsync
- Custom React Hook for using Intersection Observer:- useIntersectionObserver
Implementation
import { useState, useEffect } from 'react';
/**
* HTTP methods that can be used in the fetch request.
*/
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'; // Add other HTTP methods as needed
/**
* Options that can be passed to the hook.
* @template T The type of the data to be fetched.
*/
type AsyncOptions<T> = {
/**
* The HTTP method to use for the request.
* Defaults to 'GET'.
*/
method?: HttpMethod;
/**
* The response type to parse the response as.
* Defaults to 'json'.
*/
responseType?: 'json' | 'text' | 'blob' | 'formData' | 'arrayBuffer';
/**
* The number of times to retry the request if it fails.
* Defaults to 3.
*/
retryAttempts?: number;
/**
* A function to call when the request is successful.
* Called with the parsed response data.
*/
onSuccess?: (data: T) => void;
/**
* A function to call when the request fails.
* Called with the error that occurred.
*/
onError?: (error: Error) => void;
}
/**
* The state of the data fetched by the hook.
* @template T The type of the data to be fetched.
*/
type AsyncState<T> = {
/**
* The data fetched from the endpoint.
* Will be null if the request is still in progress or failed.
*/
data: T | null;
/**
* The error that occurred if the request failed.
* Will be null if the request is still in progress or succeeded.
*/
error: Error | null;
/**
* Whether the request is currently in progress.
*/
isLoading: boolean;
/**
* A function to abort the in-progress request.
*/
abortRequest: () => void;
}
/**
* A React hook that fetches data from an endpoint.
* @template T The type of the data to be fetched.
* @param endpoint The URL to fetch data from.
* @param options Options for the fetch request.
* @returns The state of the data fetched from the endpoint.
*/
const useAsync = <T>(endpoint: string, options?: AsyncOptions<T>): AsyncState<T> => {
const [state, setState] = useState<AsyncState<T>>({
// The data fetched from the endpoint.
// Will be null if the request is still in progress or failed.
data: null,
// The error that occurred if the request failed.
// Will be null if the request is still in progress or succeeded.
error: null,
// Whether the request is currently in progress.
isLoading: false,
});
const [controller, setController] = useState<AbortController | null>(null);
const {
method = 'GET',
responseType = 'json',
retryAttempts = 3,
onSuccess,
onError,
} = options || {};
const fetchData = async () => {
// Create a new AbortController to handle aborting the request.
const abortController = new AbortController();
setController(abortController);
// Set the loading state to true to indicate that the request is in progress.
setState({ ...state, isLoading: true });
try {
let response: Response | null = null;
let attempt = 0;
// Try to fetch the data from the endpoint up to the number of retryAttempts.
while (attempt < retryAttempts) {
attempt++;
response = await fetch(endpoint, { method, signal: abortController.signal })
if (response.ok) {
break;
}
}
// If the number of attempts was exceeded or the response is not ok,
// throw an error.
if (!response || !response.ok) {
throw new Error(`Failed to fetch data from ${endpoint}`);
}
// Convert the response to the desired type and set it as the data.
const responseData = await response[responseType]();
// Set the loading state to false to indicate that the request is complete.
setState({ data: responseData, error: null, isLoading: false });
// Call the onSuccess function if it was provided.
if (onSuccess) {
onSuccess(responseData);
}
} catch (error) {
// Set the loading state to false to indicate that the request is complete.
setState({ data: null, error, isLoading: false });
// Call the onError function if it was provided.
if (onError) {
onError(error);
}
}
};
// A function to abort the in-progress request.
const abortRequest = () => {
if (controller) {
controller.abort();
}
};
// Run the fetchData function when the component mounts and unmounts.
// The effect function is called with the dependencies array which is
// an empty array here. This means the effect will only run once when the
// component mounts.
useEffect(() => {
fetchData();
return () => {
// When the component unmounts, abort the request.
abortRequest();
};
}, []);
return { ...state, abortRequest };
};
export default useAsync;