Custom Hook For API Calls In React

Introduction

In this article, I am going to explain how to create a custom Hook for API calls in React.

React Hooks

It is a new feature that allows using React features without writing a class. Hooks are functions that help to use the state and lifecycle features in the React function components. Hooks do not work inside the class components. This feature has introduced in React 16.8.

We need to follow the below rules to use Hooks in the React

  1. Only call Hooks at the top level
    Hooks should always be used at the top level of the React functions. Do not call Hooks inside the loops, conditions, or nested functions. 
     
  2. Only call Hooks from React functions
    Cannot call Hooks from regular JavaScript functions. Instead, Call Hooks from React function components. Hooks can also be called custom Hooks.

Custom Hooks

Custom Hooks are used for building common or reusable logic and these functions are prefixed with the word "use". Ex. useTestCustomHook. 

I am using typescript for implementing the custom hook in React. I have two interfaces which are IRequestInfo and IResponseInfo

IRequestInfo

IRequestInfo defines how the request format should be. It has below properties.

Headers

Request headers like authentication, content-type, etc.

Method

It represents the API Method such as  "GET" or "PUT" or "POST" or "PATCH"

EndPoint

It is the API endpoint going to call.

RequestBody

Request Body for the API call. It is an optional one. Because the GET call doesn't have a request body.

export interface IRequestInfo {
  Headers?: {};
  Method: string; // "GET" or "PUT" or "POST" or "PATCH"
  EndPoint: string;
  RequestBody?: object;
}

IResponseInfo

IResponseInfo defines the format of the response and it has below two properties.

Data

It contains the response data or error.

hasError

It is true if the fetch is failure otherwise false. We can identify the API status using this property.

export interface IResponseInfo {
  Data: any;
  hasError: boolean;
}

I have created one custom hook function "useFetchCall".

Inside the "useFetchCall" hook,

  • Used the fetch() function to call the API
  • The hook has one argument for the initial request value.
  • The hook has three return values
    • response - It has the response of the API which is the IResponseInfo type. 
    • isFetching - It is true when calling the API and false once the response came from API
    • setRequest - Set the request for API call which is IRequestInfo type.
  • When calling the setRequest() function from any function component or other custom hooks, it will assign the request data to "requestInfo" local state inside the "useFetchCall" hook.
  • Once value is assigned to "requestInfo" local state then, the useEffect() will be execute. Because, "requestInfo" has added as dependecy for that useEffect().
  • The API calling logic will be executed inside of the useEffect().

Once an API call has successfully completed, then assign the API's response to "responseInfo" local state using "setResponse()" method. Ex. setResponse({ Data: httpResponse, hasError: false}),

export enum HttpStatusCode {
  OK = 200,
  CREATED = 201,
  SUCCESS_NO_CONTENT = 204
}

function useFetchCall(props: IRequestInfo): [IResponseInfo, boolean, React.Dispatch<React.SetStateAction<IRequestInfo>>] {

  const [isFetching, setIsFetching] = useState(false);
  const [requestInfo, setRequest] = useState(props);
  const [responseInfo, setResponse] = useState({} as IResponseInfo);

  useEffect(() => {
    if (Object.keys(requestInfo).length === 0 && requestInfo.constructor === Object)
      return;

    const promise = new Promise((resolve: any, reject: any) => {
      const fetchURL = requestInfo.EndPoint;
      const fetchData = {
        body: requestInfo.RequestBody ? JSON.stringify(requestInfo.RequestBody) : "",
        headers: requestInfo.Headers ? requestInfo.Headers : {},
        method: requestInfo.Method
      };

      fetch(fetchURL, fetchData).then((response: Response) => {
        switch (response.status) {
          case HttpStatusCode.OK:
          case HttpStatusCode.CREATED:
          case HttpStatusCode.SUCCESS_NO_CONTENT:
            response.clone().json().then((data: any) => {
              resolve(data);
            }).catch(() => {
              // Log the Error
              resolve(null);
            });
            break;
          default:
            response.clone().json().then((data: any) => {
              reject(data);
            }).catch(() => {
              // Log the Error
              reject(null);
            });
        }
      }).catch((error: Error) => {
        // Log the Error
        reject(error);
      });
    });

    setIsFetching(true);

    promise.then(
      (httpResponse: any) => {
        setResponse({ Data: httpResponse, hasError: false, });
        setIsFetching(false);
      },
      (error: Error) => {
        setResponse({ Data: error, hasError: true, });
        setIsFetching(false);
      });
  }, [requestInfo]);

  return [responseInfo, isFetching, setRequest];
}

export default useFetchCall;

How to Call "useFetchCall" Custom Hook

Declare and Initialize the "useFetchCall" hook

const [response, isFetching , setRequest] = useFetchCall({} as IUseHttpRequestInfo);

Create the request

const apiRequest = // Build you request based on "IRequestInfo"  type.

Call the API using Custom Hook by setting the request

setRequest(apiRequest);

Read the API response

React.useEffect(() => {
    if (isFetching === false && response && response.Data)
      // Implement the logic and read the response from "response.Data" object
  }, [response]);
  
React.useEffect(() => {
    if (isFetching === true)
      // Implement the logic loading
  }, [isFetching]); 

Hope you liked it and know about custom hooks in react. If you have any doubts or comments about this, please let me know in the comments.