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.

JavaScript
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.

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

I have created one custom hook function "useFetchCall".

Inside the "useFetchCall" hook,

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}),

JavaScript
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

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

Create the request

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

Call the API using Custom Hook by setting the request

JavaScript
setRequest(apiRequest);

Read the API response

JavaScript
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.