DEV Community

Cover image for How to Create a Custom Hook for Seamless Data Retrieval with React.
Makanju Oluwafemi
Makanju Oluwafemi

Posted on

4 1 1 1

How to Create a Custom Hook for Seamless Data Retrieval with React.

introduction

Custom hooks in react 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 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.

npx create-react-app hooks-example
cd hooks-example
Enter fullscreen mode Exit fullscreen mode

Also, copy this code to add axios

npm i axios
Enter fullscreen mode Exit fullscreen mode

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

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.'); 
    }
};
Enter fullscreen mode Exit fullscreen mode

In this example code, I have defined an Axios instance apiService with a baseURL and default 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 awaits 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:

import axios from "axios";

const baseURL = 'api-url/';

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

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.

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;
Enter fullscreen mode Exit fullscreen mode

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!

Your next step

Do your career a favor. Join DEV.

It takes one minute and is worth it for your career.

Get started

Top comments (1)

Collapse
 
devyoma profile image
Emore Ogheneyoma Lawrence •

Nice article 🚀. Interesting read 💯

Billboard image

Imagine monitoring that's actually built for developers

Join Vercel, CrowdStrike, and thousands of other teams that trust Checkly to streamline monitor creation and configuration with Monitoring as Code.

Start Monitoring

👋 Kindness is contagious

Explore a sea of insights with this enlightening post, highly esteemed within the nurturing DEV Community. Coders of all stripes are invited to participate and contribute to our shared knowledge.

Expressing gratitude with a simple "thank you" can make a big impact. Leave your thanks in the comments!

On DEV, exchanging ideas smooths our way and strengthens our community bonds. Found this useful? A quick note of thanks to the author can mean a lot.

Okay