Making a Custom Hook - usefetch Hook in React
Making a Custom Hook - usefetch Hook in React

Hello and welcome back to the channel. In this video you will learn how to create custom hooks on the most useful example. We will create a useFetch hook to fetch data from any API.

Here I have empty create-react-app project where we can directly start writing code. If you don't know how to generate react project with create-react-app I made a video on that so go check it out first.

I you can see in browser I have just a single h1 tag and it's our App.js component. Let's have a look. So it's a normal stateless React component with markup inside.

As we are using React bigger than 16 version (actually 17) we can use hooks inside without installing any additional packages.

So what are custom hooks? It's just a javascript function which we start with the word use and it can call other hooks inside. Previously I said that we can use hooks only inside React components. But custom hooks is the second place where we can call hooks.

Let's create a hooks folder and our useFetch hook inside.

src/hooks/useFetch.js

Now let's just create a function

const useFetch = () => {
  const doSomething = () => {};
  return ["somedata", doSomething];
};

export default useFetch;

So here is our first custom hook. It's just a function and we returned whatever we want. But here are 2 important things. First of all we name all hooks with use prefix. It is highly recommended because eslint rules can show us warning only when we put this prefix there. The second important thing is that we returned here an array with 2 argument: first is some data and second is a function. We are doing this to maintain the same response that we are getting normally from react hooks.

Now let's plan what we want to get back from our hook. Here is how I want to use it.

const [{response, isLoading, error}, doFetch] = useFetch('http://localhost:3000')

So we are using useFetch as a normal react hook as you can see we are getting back an array with 2 parameters: first is an object with data and a doFetch function. We also passed a url that we want to fetch as an argument to useFetch. Now we can call doFetch function from any place of our compoonent and it will trigger from of this specific url. In the first argument we are getting response, isLoading and errors. This are 3 things that are interesting for us from the request. So response is null by default and when we get the result back we will get data there. isLoading is a boolean property so we can know that we are doing the request and show loader in our UI. And error is null by default and if we get an error it should be there.

So that's a plan. Now let's start to implement it.

import { useState } from "react";

const useFetch = (url) => {
  const [response, setResponse] = useState(null);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);

  const doFetch = () => {};
  return [{ response, error, isLoading }, doFetch];
};

export default useFetch;

So here we created 3 states for response, isLoading and error. If you didn't watch my video about useState hook then pause this video and go watch that one first. I will link it down in the description.

We also created a doFetch function and returned everything in format that we want.

Now let's try to use our empty custom hooks.

const [{ response, error, isLoading }, doFetch] = useFetch(
  "http://localhost:3000/posts"
);

As you can see in browser we already get all our states from our hooks.

The next question is how our doFetch function will look like and what we will pass inside. So first of all about usage. I see 2 usages here: 1 when we do something, like "User filled the form and clicked on register button". In this case we want to just call doFetch function to start the API call. The second usecase is when we want to fetch some data on initialize of the component. In this case we can call doFetch inside the effect with empty brackets as a dependency. If you didn't watch my useEffect video go watch it first.

useEffect(() => {
  doFetch()
}, [])

Now the question is what we want to pass inside doFetch. I prefer to use axios for fetching data. This is actually the most popular library for making API requests. This is why I want to pass inside doFetch the same stuff that I pass as options inside axios. So it looks like this.

useEffect(() => {
  doFetch({
    method: 'post',
    data: {
      user: {}
    }
  })
}, [])

So we are passing a method and some data if needed. Obviously by default for get request we don't want to pass anything.

So with this ideas lets install axios library and try to implement such logic.

npm install axios

Now here is a tricky part because fetching data is a side effect and we want to write it inside useEffect.

const [options, setOptions] = useState({});

const doFetch = (options = {}) => {
  setOptions(options);
  setIsLoading(true);
};

So first of all we set options in doFetch as an empty object. So now if we don't pass anything it won't break. Next we are setting options to a new state property. Why? Because we want to access them later inside useEffect and we can do this only with state. Also we are setting isLoading to true because we are starting the process of loading data.

useEffect(() => {
  const fetchData = async () => {
    try {
      if (!isLoading) {
        return;
      }
      console.log("real fetching Data", url, isLoading);
      const res = await axios(url, options);
      setResponse(res.data);
    } catch (err) {
      setError(err.response.data);
    }
    setIsLoading(false);
  };

  fetchData();
}, [isLoading, url, options]);

As you can see we wrote in useEffect that we react on isLoading property to start our call. Now inside we do a axios request and wrap it inside try catch because we are using await. If we get a response we set our response state if and error then error. Also it's worth mentioning that you can't pass async function inside useEffect. This is why we need to create additional function inside and trigger it afterwards.

Also we are adding url and options to the array of dependencies just because we should not lie to React and we need to list all properties that we are using inside useEffect in the dependencies array.

If we look now in browser we get an error data of undefined because we have a network error

We can fix that by changing our error handler.

const data = err.response ? err.response.data : "Server error";
setError(data);

As you can see now our code is working and we are getting back an error.

Actually our code is almost ready but to test a real request we need to setup an API. I always recommend to use json-server as a simple fake server.

npm i -g json-server

Now we need to create a db.json file with some data inside.

{
  "posts": [
    {
      "id": "1",
      "title": "Use state hooks"
    },
    {
      "id": "2",
      "title": "Use effect hooks"
    }
  ]
}

Now we can start our fake API

json-server --watch db.json --port=3004

As you can see in localhost:3004/posts we are getting a list of posts back. Now if we reload an application we are getting back our correct data.

Our custom hook is almost ready but we get I problem. As you can see we have a warning that we need to add doFetch as a dependency to useEffect. It's a valid point because a function is also a variable so it should be also listed in the dependencies

But if we do it we will get an infinite loop. Because with every rerender our doFetch function is a new function. This is why useEffect will be triggered after every render. To avoid this we can wrap doFetch in useCallback hook. Again if you didn't watch my useCallback video pause here and go check that first.

const doFetch = useCallback((options = {}) => {
  setOptions(options);
  setIsLoading(true);
}, []);

Here we wrapped doFetch function in useCallback and this will memoize it forever as we passed an empty array as a dependency list. As you can see now we don't get an infinite loop because React uses previously created memorized version of a function.

So we successfully created quite big and complete custom hook and now we can use it everywhere in any application. I used this hook in quite big projects already so it's fully real.

If "React hooks for beginners" is too easy for you I have a full hooks course which is going 8 hours where we are creating real application from scratch. I will link it down in the description below.

📚 Source code of what we've done