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
usingfetch
API- or any other one you're comfortable with- in theuseRequest
functionconst 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 functionrequestHandler
which contains the logic to send HTTP Request. The function takes two argumentsconfigData
andapplyData
, the first argumentconfigData
is an object containing HTTP Requests methods, headers etc. While the second argumentapplyData
is a JavaScript function that consumes the response data. Notice how thefetch
methods have been left without values? Well, this is because theuseRequest
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
anderror
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 getconfigData
as a parameter. This parameter is a JavaScript object that must be passed when calling therequestHandler
hook anywhere in your code, It should containurl
method
body
andheader
fields to configure thefetch
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 ofbody
tofalse
if you're not attaching a payload e.g inGET
requests.Fourthly, the Addition of the
return
statement, you typically want to return the state management variables such aserror
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 istrue
.Next, handle the
fetch
response data by passingresult
intoapplyData
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. TheuseCallback
is likeuseEffect
but for functions, it takes two arguments: The callback function and an array of dependencies. Also, you need to refactor yourrequestHandler
function to a function declaration syntax, to use theuseCallback
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()
.