React Custom Hooks: How To Build your own useRequest Hook.

React Custom Hooks: How To Build your own useRequest Hook.

Introduction

One of the most important and unique features of React is the introduction of Hooks. The React Hooks are JavaScript functions that are used to Isolate reusable parts of components. In simpler terms, they are functions you can call over and over again in your components to perform specific tasks. To use the hooks in your components, you need to import them. There are generally two classes of Hooks in React, viz: The Built-in Hooks and The Custom hooks.

The built-in hooks are the hooks that are provided by the React library, they are named with the prefix use e.g useState ,useEffect ,useContext etc. In this article, you'll learn what custom hooks are and how to build your own useRequest hook for data posting and fetching in your React App.


Prerequisites

To properly understand this article you must have a basic knowledge of JavaScript, HTTP Requests, React and React-Hooks. If you are struggling with HTTP Requests in JavaScript, check out this article I wrote to simplify it.

But do not fret, I'll break the concept down to the smallest bits.


What are Custom Hooks?

Custom Hooks are reusable JavaScript functions, built by you- the developer- to perform a particle task in your React components. Conventionally the custom hooks are named with camelCase using the prefix use.

You may be wondering, Why do I need to build a custom hook? Well, Custom Hooks are typically built to handle side effect tasks such as fetching data, setting timers, saving data to local storage etc. They are built to handle functions that do not render or update DOM elements directly.


Guide to Creating a useRequest Hook

Initialize Your React App

Create a new React app using create-react-app in your terminal as shown below:

npm create-react-app

Create a Hook folder in the Root Directory

In your React App's root directory, create a new folder named Hooks. This folder will hold and manage all the custom hooks in your app

Create a useRequest.js File

In your Hook folder create the file useRequest.js, It is this file that will contain the logic for your custom useRequest hook. Do not forget to import your React dependencies!

Create a useRequest Function

In the useRequest.js file initialize a function named useRequest

const useRequest=()=>{}

Now, Compose the Hook with logic step-by-step.

  • Firstly, since you need a hook that sends HTTP requests, build an HTTP request function requestHandler using fetch API- or any other one you're comfortable with- in the useRequest function

      const useRequest=()=>{
        async function requestHandler(configData,applyData){
           try {
          const response = await fetch(// url will later be added here ,
                {
                  method: // Any HTTP Methode
                  body: // Body may or may not be use since we may need to GET
                  headers: //Any HTTP header
                })
    
              if (!response.ok) {
              throw new Error("Something went wrong")//Checks If response is ok    
                  }
             } catch (error) { console.log(error) }
    
      }
      }
    

    In the code snippet above, the useRequest function has been populated with a function requestHandler which contains the logic to send HTTP Request. The function takes two arguments configData and applyData , the first argument configData is an object containing HTTP Requests methods, headers etc. While the second argument applyData is a JavaScript function that consumes the response data. Notice how the fetch methods have been left without values? Well, this is because the useRequest Hook should be reusable thus, these values should not be hard coded.

  • Secondly, you need to handle states. What does this mean? well, take for example, If the HTTP request fails you need to inform the user their request has failed, or if the request is being loaded you also need to inform your users appropriately; nobody likes to submit a request and not get a response on what is currently happening. In this Hook, you should handle two states, which are: isLoading and error These states handle loading and error respectively as shown in the code snippet below.

      //since we need the useState hook, we need to import it
      import { useState } from "react";
    
      const useRequest=()=>{
      const [error, setError] = useState(null)
      const [isLoading, setIsLoading] = useState(false)
    
       async function requestHandler(configData,applyData){
            setIsLoading(true); //to indicate when request starts processing
            setError(null)//Initialise Error as null before every request
            try {
              const response = await fetch(// url will later be added here ,   
               {
                  method: // Any HTTP Methode
                  body: // Body may or may not be use since we may need to GET
                  headers: //Any HTTP header
                } )
    
              if (!response.ok) {
                  throw new Error("Something went wrong") //Checks If response       
                  //is okay, throws an error if its not
                  }
             } catch (error) {
              setError(error) //set error and error message
             }
             setIsLoading(false)//to indicate when requests stops processing
       }
      }
    
  • Thirdly, your requestHandler function should get configData as a parameter. This parameter is a JavaScript object that must be passed when calling the requestHandler hook anywhere in your code, It should contain url method body and header fields to configure the fetch API request object(the second parameter)

      import { useState } from "react";
    
         const useRequest=()=>{
            const [error, setError] = useState(null)
            const [isLoading, setIsLoading] = useState(false)
             async function requestHandler(configData, applyData){
              setIsLoading(true); //to indicate when request starts processing
              setError(null)//Initialise Error as null before every request
             try {
              const response = await fetch( configData.url,
                 {
                  method: configData.method;
                 //to check if body field is true asin PUT and POST request
                  body: configData.body? JSON.stringify(configData.body): null;
                  headers: configData.header;
                }
              );
    
             if (!response.ok) {
               throw new Error("Something went wrong")//Checks Ifresponse is ok     
                }
             } catch (error) {
               setError(error)
            }
              setIsLoading(falsely)
         }
      }
    

    while defining the configData object, it is important to set the value of body to false if you're not attaching a payload e.g in GET requests.

  • Fourthly, the Addition of the return statement, you typically want to return the state management variables such as error loading and the request function -requestHandler -in an object. This is illustrated in the code below:

    
      import { useState } from "react";
    
      const useRequest=()=>{
      const [error, setError] = useState(null)
      const [isLoading, setIsLoading] = useState(false)
    
      async function requestHandler(configData,applyData){
          setIsLoading(true)
          setError(null)
             try {
                const response = await fetch( configData.url,
                {
                  method: configData.method;
                 //to check if body field is true asin PUT and POST request
                  body: configData.body? JSON.stringify(configData.body): null;
                  headers: configData.header;
                });
                  if (!response.ok) {
                  throw new Error("Something went wrong") //Checks If response       is okay
                  }
             } catch (error) {
                 setError(error)
            }
                setIsLoading(false)
           }
           return {error, isLoading, requestHandler}
       }
    

    the returned variables can then be used for state management and conditional rendering in your app. for example you can create a modal that shows "Loading..." when the isLoading variable is true.

  • Next, handle the fetch response data by passing result into applyData function.

      import { useState } from "react";
    
      const useRequest=()=>{
      const [error, setError] = useState(null)
      const [isLoading, setIsLoading] = useState(false)
    
        async function requestHandler(configData, applyData){
           setIsLoading(true)
           setError(null)
           try {
               //handling the http request
              const response = await fetch( configData.url,
                {
                  method: configData.method;
                 //to check if body field is true asin PUT and POST request
                  body: configData.body? JSON.stringify(configData.body): null;
                  headers: configData.header;
                }
                );
    
             //handling Data
               const result= await response.json()
               applyData(result)
    
              //Handling error
              if (!response.ok) {
               throw new Error("Something went wrong")//Checks If response isOk     
                 }
             } catch (error) {
                 setError(error)
              }
                setIsLoading(false)
          }
            return {
                 error, 
                 isLoading,
                 requestHandler
                }
        }
    
  • Next, memoize the requestHandler function using the built-in React Hook-useCallback.To ensure the function only changes when one of its dependencies changes. The useCallback is like useEffect but for functions, it takes two arguments: The callback function and an array of dependencies. Also, you need to refactor your requestHandler function to a function declaration syntax, to use the useCallback hook.

      import { useState } from "react";
      import {useCallback} from "react"
    
      const useRequest=()=>{
      const [error, setError] = useState(null)
      const [isLoading, setIsLoading] = useState(false)
      const [data,setData] = useState(null)
    
         const requestHandler= useCallback(async (configData, applyData)=>{
            setIsLoading(true)
            setError(null)
            try {
               //handling the http request
              const response = await fetch( configData.url,
                {
                  method: configData.method;
                 //to check if body field is true asin PUT and POST request
                  body: configData.body? JSON.stringify(configData.body): null;
                  headers: configData.header;
                }
                );
    
              //Handling error
              if (!response.ok) {
                  throw new Error("Something went wrong") //Checks If response       
                 // is okay, it throws an error if it"s not.
              }
              //handling Data
               const result= await response.json()
               applyData(result)
               })
    
             } catch (error) {
                 setError(error)
              }
                setIsLoading(false)
          },  []  )
    
             return {
                 error: error, 
                 isLoading: isLoading,
                 requestHandler:requestHandler
                }
        }
    

    In this example, the array of dependencies has been left empty because the request needs to be sent once.

Finally, export the useRequest hook. The final step is to export the useRequest hook so you can use it in anywhere where in your app after import

import { useState } from "react"
import {useCallback} from "react"

const useRequest=()=>{
const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(false)

    const requestHandler= useCallback(async (configData, applyData)=>{
       setIsLoading(true)
       setError(null)
      try {
         //handling the http request
        const response = await fetch( configData.url,
          {
            method: configData.method;
           //to check if body field is true asin PUT and POST request
            body: configData.body? JSON.stringify(configData.body): null
            headers: configData.header;
          }
          );

        //Handling error
        if (!response.ok) {
            throw new Error("Something went wrong") //Checks If response       
           // is okay, it throws an error if it"s not.
         }
         //handling Data
         const result= await response.json()
         applyData(result)
         })
      } catch (error) {
           setError(error)
        }
          setIsLoading(false)
    }, [] )

     return {
           error: error, 
           isLoading: isLoading,
           requestHandler:requestHandler
          }
  }
export default useRequest;

after this, you'll be able to access your useRequest hook in any component as shown in the code snippet below:

import React from "react";
import useRequest from "./hook/useRequest.js"

function App(){
const {error,isLoading,requestHandler}= useRequest()
}

Summary

In the previous section, you have learned using a step-by-step guide, how to create a useRequest custom hook. Below is the full code without the extra annotations.

 import { useState, useCallback } from 'react';

const useRequest = () => {
  const [error, setError] = useState(null)
  const [isLoading, setIsLoading] = useState(false)

  const requestHandler = useCallback(async (configData, applyData) => {
    setIsLoading(true)
    setError(null)
    try {
      const response = await fetch(configData.url, {
        method: configData.method,
        body: configData.body ? JSON.stringify(configData.body) : null,
        headers: configData.header

      })

      if (!response.ok) {
        throw new Error('Something went Wrong')
      }

      const result = await response.json()
      applyData(result)
    } catch (errpr) {
      setError(error)
    }
    setIsLoading(false)
  }, []);

  return {
    error: error,
    isLoading: isLoading,
    requestHandler: requestHandler,
  }
}

export default useRequest;

There you have it, folks, you have learned how to create a custom Hook to handle HTTP requests in React. Feel free to practice creating other custom Hooks.

Like, share and leave a comment below. until next time folks; code(), eat(), sleep() and repeat().