DEV Community

Cover image for Building custom hooks in React to fetch Data
Shahid Rizwan
Shahid Rizwan

Posted on

Building custom hooks in React to fetch Data

Fetching data from the backend is one of the crucial parts of the web application. For every application to work dynamically, it fetches the data from the server and then displays it in the user interface.

We retrieve the data using the API call and using built-in React hooks like useState, useEffect, and useReducer, the retrieved data is set to the state variable. It is then used in components for showing it in the view.

Before jumping into the code of how's it's done, let's first look at what React hooks are and why it is used.

What is React Hooks

React hooks were first introduced in React 16.8. They are functions that let you hook into React state.

Some of the built-in hooks provided by React are useState, useEffect, useContext, useReducer, useRef, useCallback, and useMemo.

Why React Hooks are used

One of the main advantages of using React hooks is the re-usability of logic. The hooks can be used in multiple components where we have to use a specific function.

It also makes the code more readable, efficient, and easy to maintain.

The normal code for fetching the data from the server and updating in the component is shown below

export function Home(){

    const [data,setData] = useState(null)
    const [loading,setLoading] = useState(false)
    const [error,setError] = useState(null)

    useEffect(()=>{
        (
        async function(){
            try{
                setLoading(true)
                const response = await axios.get('http:localhost:4000')
                setData(response.data)
            }
            catch(err){
                setError(err)
            }finally{
                setLoading(false)
            }
        }
        )()
    },[])

    return(
        {loading && <div>Loading...</div>}
        {data && <div>{data}</div>
    )
}
Enter fullscreen mode Exit fullscreen mode

We write the logic inside the useEffect hook to update the state properties like data, loading, and error.

While it's perfectly fine to write like this, what if we want to do the same kind of thing in multiple components where we have to fetch another data.

We have to rewrite all of these codes multiple times in all of those components which is not very efficient and hard to manage.

In big codebases, it is better to follow the Don't Repeat Yourself (DRY) principles, that is, it's better to write code once and make it reusable instead of writing it again and again in multiple components.

That's where the real magic of Custom Hook is. We can write the code in a separate js file and call it with URL from all the components that might need to fetch the data from the server.

This makes the code efficient and easily maintainable.

Like useState and useEffect have their function, we create custom hooks by combining them for a specific ability.

Creating custom useFetch hook

We first create a new javascript file with the name useFetch.js.
The name of the hooks starts with use as a part of react hooks convention.

useFetch javascript file

Inside the file, create a new function with the name of the hook. The difference between React hook and a React component is that hook doesn't return JSX. It only returns the state variable or function that you want to use in a component.

export function useFetch(){

}
Enter fullscreen mode Exit fullscreen mode

To make an API call, use a useEffect hook because it will trigger the API call function inside it when rendered. Here, the API call is made using Axios.

The API Url that needs to be called is passed to the hook as an argument from the component.

import { useEffect } from "react"
import axios from axios

export function useFetch(url){
   useEffect(()=>{
      (
         async function(){
            const response = await axios.get(url)
         }
      )()
   },[url])

}
Enter fullscreen mode Exit fullscreen mode

Usually, we have 3 state variables that are data, error, and loading created using useState to store the response data, error and loading respectively,

If the data is received, we set it to the data variable. If not, the error message will be set to the error variable.

Loader is initialized as false. When the API is called, it is set to true so that a loader component can be loaded in the view.

At the end of the API call, this loader is set back to false by using the finally block.

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


export default function useFetch(url){

    const [data,setData] = useState(null)
    const [error,setError] = useState(null)
    const [loading,setLoading] = useState(false)

    useEffect(() => {
        (
            async function(){
                try{
                    setLoading(true)
                    const response = await axios.get(url)
                    setData(response.data)
                }catch(err){
                    setError(err)
                }finally{
                    setLoading(false)
                }
            }
        )()
    }, [url])

    return { data, error, loading }

}
Enter fullscreen mode Exit fullscreen mode

The only dependency we're going to put in the useEffect dependency array is Url because if the Url changes, we have to request new data.

That's basically for useEffect. Now we return the states that are created inside the hook as an object.

Using Custom Hook in the Component

Inside the component, import the useFetch hook from its javascript file. After importing, call the hook with the API Url as an argument.

Fetching data using custom Hook

export function Home(){
    const {data,loading,error} = useFetch('https://localhost:4000')

        if(error){
           console.log(error)
        }

    return(
        {loading && <div>Loading...</div>}
        {data && <div>{data.map(item => <div>{item}</div>)}</div>}
    )
}
Enter fullscreen mode Exit fullscreen mode

In addition to this, we can also customize the hook by making it return any function that can be called from the component.

For example, we can create a refetch() function inside the hooks that re-fetches the API when called.

This function can be returned from the hook and can be called from the component.

Top comments (21)

Collapse
 
lowlifearcade profile image
Sonny Brown • Edited

Good stuff. I like to use a custom hook I made called useStateAndLocalStorage. I love the self documenting nature of custom hooks.

Collapse
 
morganjay profile image
James Morgan

Please I'd like to see how that works. 🤔

Maybe I'll like it too

Collapse
 
imrishabh18 profile image
Rishabh Gupta

Check out my blog on this James.

dev.to/imrishabh18/simplest-custom...

Thread Thread
 
morganjay profile image
James Morgan

Thanks!

Thread Thread
 
lowlifearcade profile image
Sonny Brown

That's pretty close to what I made.

Collapse
 
markyangit profile image
Gabriel Markyan

A good thing to note about this is that using custom hooks and hooks inside a useEffect causes an infinite loop.

Collapse
 
ehaynes99 profile image
Eric Haynes • Edited

That's not any simpler... It's just using the old Promise api...

 
ehaynes99 profile image
Eric Haynes • Edited

Note that the former reply was someone else, so no one "kept using 'old'". It is, in fact, older, but we can refer to it as "the .then syntax".

To each their own, but the allure of async/await is that the code reads exactly the same as it would if it were synchronous. There is a valid argument for consistency. E.g. it's pretty much universally accepted that we don't intermittently use callbacks for async, even if it would save a line or 2.

It's not "way more complex", either. It's slightly more verbose, and only in a rare set of cases. The .then syntax is only cleaner where you can constrain the action to lambda expressions and have at least 4 of them. As soon as you need a block for any of them, IMO it's rather messy. What is simply another line with async/await becomes a new function, another level of nesting, and a new scope. It takes a hit for readability when shorter than 4 as well, since formatters like prettier will inline:

myApi.getOrder(123).then(setOrder).catch(setError);
Enter fullscreen mode Exit fullscreen mode

So yes, in the cases where you have the catch in the same place as the call, can chain 4 or more operations that are all one liners, and never need to refer to something in a prior step of the chain, it's slightly cleaner, but it's rare enough that I wouldn't fault anyone for just forgetting about the syntax altogether.

const fetch1 = (orderNum) => {
  fetch(`/api/orders/${orderNum}`)
    .then((response) => response.json())
    .then(({ products }) => {
      setProducts(products);
      setError(null);
    })
    .catch((error) => {
      logger.error(error);
      setError(error);
    })
    .finally(() => setLoading(false));
};

// vs
const fetch2 = async (orderNum) => {
  try {
    const response = await fetch(`/api/orders/${orderNum}`);
    const { products } = await response.json();

    setProducts(products);
    setError(null);
  } catch (error) {
    logger.error(error);
    setError(error);
  }
  setLoading(false);
};
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
shifi profile image
Shifa Ur Rehman

And Finally!

 
hernanfarruggia profile image
Hernán Farruggia

I don't see any complexity in any of those... 🤷🏻‍♂️ They just have different sintax, and usually which one to use, will depend on the conventions of your project... Some companies decide to go with async to use latest festures, others prefer to stay with plain old promises... These sre just different points of view...

 
marceliwac profile image
Marceli-Wac • Edited

There is also the useCallback hook which can be used to create a memoized function that, when passed to useEffect dependency array, doesn't triger re-renders.

Collapse
 
mnlfischer profile image
Manuel

You should also check:
kent.dev/blog/stop-using-isloading...

Collapse
 
shaedrizwan profile image
Shahid Rizwan

Sure Manuel. Thank you for the article.

Collapse
 
khasky profile image
Khasky
Collapse
 
slimani_21 profile image
Slimani Mohammed

Thanks for the article. I think it would be better to set the error to null before fetching so that one error in fetching won't stay for all future fetch calls.

Collapse
 
rahulxyz profile image
Rahul Gupta

How would you handle different http methods? Here you have 1 hook using only get().

Collapse
 
shaedrizwan profile image
Shahid Rizwan

This hook is specifically for fetching data (GET). You can customize it by passing the HTTP method and body as parameter to the hook and use it inside.

Collapse
 
juancor181192 profile image
Juan Carlos Otero Roca

I use axios

 
marceliwac profile image
Marceli-Wac

I'm not sure I understand what you mean, can you elaborate?

Collapse
 
markyangit profile image
Gabriel Markyan

Yes, of course. But some linters require all the functions, custom hooks and external data at the dependency array. I'm saying because I've been through this.

Collapse
 
mustafazahedi profile image
Mustafa Zahedi

How fetch again after submitting any form if need in some case?