7 mins read


Custom react hook for API call:- useAsync


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;