How to Create a Custom Hook for Seamless Data Retrieval with React.
Tags: react, webdev, javascript, frontend
Published: 2024-04-30 13:32:17

## introduction

Custom hooks in [react](react.dev) allow developers to extract logic that is needed in multiple components into a standalone function. This can apply to making API requests, state management, or even side effects.

Side effects in React refer to any operation that occurs outside the scope of the component's render function, such as data fetching, subscriptions, or DOM manipulation.

There are a few rules that govern hooks creation in React; you can check them [here]: https://legacy.reactjs.org/docs/hooks-rules. However, make sure your hook name starts with `use`.

## Create a Service That Handles API Requests
When working on features that require logic duplication, for example, you need to send a [GET] (https://www.w3schools.com/tags/ref_httpmethods.asp) request to `/api/allproduct`, `/api/singleproduct/:id` and so on. You can decide to create a service that handles all these requests in a single file and export them to use in another file.

To understand what I'm saying in essence, walk with me!
Do ensure that you have a React project set up already, or copy this code to do that.
```bash
npx create-react-app hooks-example
cd hooks-example
```
Also, copy this code to add [axios] (https://www.npmjs.com/package/axios)
```bash
npm i axios
```
Now that you are all set, create a folder and name it `service` in your `src` directory. Inside this folder, create an 
`evenService.js` file.

`eventService.js`
```javascript
import axios from "axios";

const baseURL = 'api-url/';

const apiService = axios.create({
    baseURL,
    headers: {
        'Content-Type': 'application/json',
    }
});

export const fetchAllProduct = async () => {
    try {
        const res = await apiService.get('api/all-products');
        if (res.data.status === 200) {
            return res.data.products; // Return only the products data
        } else {
            throw new Error(`Failed to fetch all products: ${res.data.message}`); // Include more descriptive error message
        }
    } catch (error) {
        throw new Error('Failed to fetch all products. Please try again.'); 
    }
};
``` 
In this example code, I have defined an Axios instance `apiService` with a `baseURL` and default [headers] (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers) for making HTTP requests to an API. It also defines a function `fetchAllProduct` for fetching all products from the API using a GET request.

### Is this approach better?
I won't say it's not better, but then things can be implemented better by putting this logic into a custom hook called `useApi`. 

**Here is my take**
If I continue like this, the `eventService.js` file will soon become so long and unorganized. I will have to add a new export function for each API request. Rewriting the [async](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) [awaits](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await) for everyone of the function. 
This does not seem to be perfect; remember the DRY (Do Not Repeat Yourself) rule.

## Create a Custom Hook for API requests
You can edit the `eventService.js` file by removing the `fetchAllProduct` function. 

Then add export to `apiService` like this:
```javascript
import axios from "axios";

const baseURL = 'api-url/';

export const apiService = axios.create({
    baseURL,
    headers: {
        'Content-Type': 'application/json',
    }
});
```

Create a folder and name it `hooks` in your `src` directory. Then, create a `useApi.js` file. Import your `apiService` and use it here.

Copy and paste this code.
```javascript
import { useState, useEffect } from "react";
import  { apiService } from '../service'

const useApi = (endpoint, method = 'GET', req = null) => {
   const [loading, setLoading] = useState(true);
   const [data, setData] = useState(null);
   const [error, setError] = useState(null);

   useEffect(() => {
     const handleEndpoint = async () => {
       setLoading(true);
       try {
         let response;
         switch(method.toUpperCase()) {
           case 'GET':
             response = await apiService.get(endpoint);
             break;
           case 'POST':
             response = await apiService.post(endpoint, req);
             break;
           case 'PUT':
             response = await apiService.put(endpoint, req);
             break;
           case 'DELETE':
             response = await apiService.delete(endpoint);
             break;
           default:
             throw new Error(`Unsupported HTTP method: ${method}`);
         }
         setData(response.data);
       } catch (error) {
         setError(error);
       } finally {
        setLoading(false);
       }
     };

     handleEndpoint();
   }, [endpoint, method, req]);

   return { loading, data, error };
};

export default useApi;
```

In this example, we created a custom hook called `useApi`. It encapsulates API fetching logic in a reusable manner.  `useState` and `useEffect` hooks are used to manage loading state, fetched data, and potential errors. When invoked with a specified API endpoint, HTTP method, and optional request body (req), it triggers an asynchronous operation to fetch data from the API using the provided method (GET, POST, PUT, DELETE). Upon completion, it updates the component's state accordingly, setting loading to false, storing the retrieved data in data, and capturing any errors in error. 

This hook allows reusability, reduces the complexities of API interaction, and promotes cleaner code.

With these few points of mine, I hope I have been able to convince and not confuse you that creating a custom hook for your API request will significantly advance modularity and reusability. Happy Coding!