How To Create Custom Hooks in React / Next JS
Tags: react, nextjs, javascript, reacthooks
Published: 2023-06-27 08:14:11

## What are React Hooks?

React Hooks allows developers "hook into" React's lifecycle and state management features from functional components. They provide a more concise and reusable way to manage component logic. Some popular examples are useState and useEffect Hooks.

**Prerequisites**

* Good understanding of react js hooks like useState and useEffect.
    
* You have a react project running that you want to create a custom hook for.
    

### Why should we use custom hooks?

React Hooks helps us to reuse logic across our code base. You've written a piece of Code that you later found out other files that might need that logic, a good practice is to abstract that logic into a custom hook.

For example, you've written a functionality that allows you to communicate with an API, this logic includes, returning errors, returning the success messages, and sending data to the API. It's always a good practice to abstract this logic into a hook so you can reuse it across your codebase so that you don't have to rewrite it in every file you need that logic. This helps to make your code more organized and less bulky with repeated logic. One clean Code principle is DRY ie Do not Repeat Yourself, which means if a code is used multiple times across a file, you turn it into a single code block or into a file and export it from that file to be used across your application.

### Here are some rules you need to take note of when creating custom hooks

1. Never call Hooks inside loops, conditions, or nested functions.
    
2. Always use Hooks at the top level of your React component.
    
3. Never call Hooks from regular JavaScript functions.
    
4. Instead, call Hooks from React functional components or call Hooks from custom Hooks.
    
5. Make use of the "use" prefix to name your functions and the filename eg: useEffect, useState, useFetch.
    

### What are we going to do?

We're going to utilize [JSON Placeholder Fake Data API](https://jsonplaceholder.typicode.com) to fetch dummy data that we are going to use for our application.

When interacting with an API, we would need a library to interact with that API, for that we are going to use [Axios](https://www.npmjs.com/package/axios) for this.

To install Axios When using NPM, run this command on your terminal.

`npm install axios`

When using Yarn, run this command on your terminal.

`yarn add axios`

Create a `Home.js` file on your react app and paste the code below

```javascript
// Home.js

import { useState, useEffect } from "react";
import axios from "axios";

const Home = () => {
  const [data, setData] = useState(null);
  const [isLoading, setIsLoading] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    handleGetData();
  }, []);

  const handleGetData = async () => {
    setIsLoading(true);
    try {
      const getData = await axios.get(
        "https://jsonplaceholder.typicode.com/todos"
      );
   
      if (getData.status == "200") {
        setData(getData.data);
        setIsLoading(false);
      }
    } catch (error) {
      setError(error);
      throw console.error(error);
    }
  };

  return (
    <>
      {isLoading && <p>Loading...</p>}
      {error && (
        <div>
          There was an Error <button onClick={handleGetData}>Try Again</button>{" "}
        </div>
      )}
      {data &&
        !isLoading &&
        data.map((item) => {
          return <p key={item.id}>{item.title}</p>;
        })}
    </>
  );
};

export default Home;
```

you should see this on the web page

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1687605564204/fb07e8a6-e3bd-4fbc-8aca-1a216c9d35ac.png align="center")

**This Home.js File does 6 basic things**

1. Fetching the data from JSON Placeholder API.
    
2. Set that response data to a state and a loading state
    
3. Catches any error on the file and sets it to an error state.
    
4. Displays if there was an error getting the data for the user to see.
    
5. Shows the loading state for the user to see.
    
6. Displays the Data for the user to see
    

This is the basic functionality for pages that query an API hence the need for us to abstract this logic. On a typical API you would have different endpoints like /createAccount or /getUser and to implement it you'll have to rewrite this logic for all files. So let's abstract this into a reusable logic.

On the JSON Placeholder API, we have access to these routes, so it would make sense for us to allow our custom hook to accept any suffix link

<table><tbody><tr><td colspan="1" rowspan="1"><p><a target="_blank" rel="noopener noreferrer nofollow" href="https://jsonplaceholder.typicode.com/posts" style="pointer-events: none">/posts</a></p></td><td colspan="1" rowspan="1"><p>100 posts</p></td></tr><tr><td colspan="1" rowspan="1"><p><a target="_blank" rel="noopener noreferrer nofollow" href="https://jsonplaceholder.typicode.com/comments" style="pointer-events: none">/comments</a></p></td><td colspan="1" rowspan="1"><p>500 comments</p></td></tr><tr><td colspan="1" rowspan="1"><p><a target="_blank" rel="noopener noreferrer nofollow" href="https://jsonplaceholder.typicode.com/albums" style="pointer-events: none">/albums</a></p></td><td colspan="1" rowspan="1"><p>100 albums</p></td></tr><tr><td colspan="1" rowspan="1"><p><a target="_blank" rel="noopener noreferrer nofollow" href="https://jsonplaceholder.typicode.com/photos" style="pointer-events: none">/photos</a></p></td><td colspan="1" rowspan="1"><p>5000 photos</p></td></tr><tr><td colspan="1" rowspan="1"><p><a target="_blank" rel="noopener noreferrer nofollow" href="https://jsonplaceholder.typicode.com/todos" style="pointer-events: none">/todos</a></p></td><td colspan="1" rowspan="1"><p>200 todos</p></td></tr><tr><td colspan="1" rowspan="1"><p><a target="_blank" rel="noopener noreferrer nofollow" href="https://jsonplaceholder.typicode.com/users" style="pointer-events: none">/users</a></p></td><td colspan="1" rowspan="1"><p>10 users</p></td></tr></tbody></table>

### **Step 1: Identify the Code to be reused.**

It's good practice to put files that are performing similar logic into the same folder, you should have a hooks folder to hold all the custom hooks you'll create for your project. So create a hooks folder and a file called useRequest.js

Your directory should look something like this.

```javascript
-src
--\hooks\useRequest.js
```

Let's copy the necessary re-useable logic from our index.js file into our `useRequest.js` file, which should look like this.

```javascript
// useRequest.js

import { useState } from "react";
import axios from "axios";

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


  const handleGetData = async () => {
    setIsLoading(true);
    try {
      const getData = await axios.get(
        "https://jsonplaceholder.typicode.com/todos"
      );
     
      if (getData.status == "200") {
        setData(getData.data);
        setIsLoading(false);
      }
    } catch (error) {
      setError(error);
      throw console.error(error);
    }
  };

  return { data, isLoading, error, handleGetData };
};

export default useRequest;
```

### Explanation of the code

```javascript
  const [data, setData] = useState(null);
  const [isLoading, setIsLoading] = useState(null);
  const [error, setError] = useState(null);
```

These are the states that would handle the response from the API, the loading state, and the error state.

```javascript
  const handleGetData = async () => {
    setIsLoading(true);
    try {
      const getData = await axios.get(
        "https://jsonplaceholder.typicode.com/todos"
      );
     
      if (getData.status == "200") {
        setData(getData.data);
        setIsLoading(false);
      }
       else{
        setError("There was an error");
      }
    } catch (error) {
      setError(error);
      throw console.error(error);
    }
  };
```

handleGetData is an [async](https://www.geeksforgeeks.org/understanding-the-async-in-javascript/) function, here's what it does.

1. It first sets the loading state to true, because we want to show a loading state while our hook is calling the endpoint.
    
2. We're wrapping the main logic in a try/catch block because we want to handle the errors we might get, errors such as network errors, a request timeout or a data not found error.
    
3. The next line calls the API with [axios](https://www.npmjs.com/package/axios), checks if the status is 200(ie, there was a response with data), and sets the data and loading state to false, if the error state is not 200, it means there was an error, so we handle that.
    

```javascript
return { data, isLoading, error, handleGetData };
```

The return statement is important because you might not always want to make all the functions or variables accessible on your hook, so the return allows us to specify what we want to expose. Here we are exposing the response of our API call(data), loading, and error states, and handleGetData function which we explained earlier.

### Step 2: How to use it

Since we're done abstracting the reusable functionality into a hook, here's how to use it.

```javascript
  const { data, isLoading, error, handleGetData } = useRequest();
```

Just looking at this, you'll see that it's pretty similar to how you'll use the basic react hooks like useState or useEffect.

Here's how our `Home.js` file now looks

```javascript
// Home.js

import { useState, useEffect } from "react";
import useRequest from "../../hooks/useRequest";

const Home = () => {

  const { data, isLoading, error, handleGetData } = useRequest();

  useEffect(() => {
    handleGetData();
  }, []);

  return (
    <div>
      {isLoading && <p>Loading...</p>}
      {error && (
        <div>
          There was an Error <button onClick={handleGetData}>Try Again</button>{" "}
        </div>
      )}
      {data &&
        !isLoading &&
        data.map((item) => {
          return <p key={item.id}>{item.title}</p>;
        })}
    </div>
  );
};

export default Home;
```

We can see that we've reduced the code to virtually under 30 lines of code for us, which is a good thing, we've abstracted the logic for calling the API into a custom hook that we are going to make more re-useable now.

If you've read to this point, you already know the basics of creating custom hooks in React, what we are going to do in the next section is to make the hook accept any endpoint and base API link.

### Step 3: Make the hook more reusable.

What we are going to do here is to allow our useRequest.js function to accept two arguments, ie. `BASE_API` and `suffix`.

Our `BASE_API` is the most common url we can find on all our endpoints.

The `suffix` is the different endpoints.

For example `BASE_API: www.simpleapi.com`, `suffix: /posts`, so together the full URL would look like `www.simpleapi.com/posts`.

Here are the changes we would be making to our useRequest.js

In your Home.js file replace `const { data, isLoading, error, handleGetData } = useRequest()` with `const useRequest = (BASE_API, suffix) => { ...... }` This just shows the function accepting the `BASE_API` and `suffix.`

In your useRequest.js replace `const getData = await axios.get("https://jsonplaceholder.typicode.com/todos");` with `const getData = await axios.get(BASE_API + suffix);`

Implement this for Axios to correctly get the `BASE_API` and `suffix` endpoint.

Our final useRequest.js file looks like this.

```javascript
// useRequest.js

import { useState } from "react";
import axios from "axios";

const useRequest = (BASE_API, suffix) => {
  const [data, setData] = useState(null);
  const [isLoading, setIsLoading] = useState(null);
  const [error, setError] = useState(null);

  const handleGetData = async () => {
    setIsLoading(true);
    try {
      const getData = await axios.get(BASE_API + suffix);
  
      if (getData.status == "200") {

        setData(getData.data);
        setIsLoading(false);

      } else {
        setError("There was an error");
      }
    } catch (error) {

      setError(error);
      
      throw console.error(error);
    }
  };
  return { data, isLoading, error, handleGetData };
};

export default useRequest;
```

A good practice is to keep your BASE\_API endpoint on your .env file and your suffix on the file you want to use it on.

For example, let's say we want to create a page for comments all we need to do is to create `const suffix="`[`/comments`](https://jsonplaceholder.typicode.com/comments)`"` and use it like this

```javascript
// DisplayComment.js
 
const BASE_API = "https://jsonplaceholder.typicode.com";
  const suffix = "/comments";

  const { data, isLoading, error, handleGetData } = useRequest(
    BASE_API,
    suffix
  );
```

## Conclusion

In this tutorial, we created a react hook for handling custom interactions with an API, we did this because we wanted to abstract the functionalities into a separate file. This helps us to modularize our code and ensure that we don't repeat the same code in multiple files.

If you found this article helpful please like or leave a comment, share it with your friends/network who might need this, and connect with me on [Linkedin](https://www.linkedin.com/in/mitchel-inaju/), [Twitter](https://twitter.com/mitchelinaju) and [hashnode](https://mitchel.hashnode.dev).