DEV Community

Cover image for DIY: Writing custom React Hooks to perform async operations
Ankur Kedia
Ankur Kedia

Posted on • Updated on • Originally published at ankurkedia.com

DIY: Writing custom React Hooks to perform async operations

This article is about creating your own React Hooks to perform async operations. As an example, we will create a custom useFetch Hook to make API calls.

What are Hooks? 

Hooks are functions which let you use state and other React features without writing a class. They are a new addition in React 16.8. You can check the overview of Hooks before moving ahead. One important thing about Hooks is that they can only be used either in a functional component or inside another Hook.

Why custom Hooks?

A custom Hook allows you to extract some components logic into a reusable function. It is a reusable Javascript function that can call other Hooks.

Rules of Hooks

Hooks are JavaScript functions, but they impose two additional rules:

  • Only call Hooks at the top level. Don't call Hooks inside loops, conditions, or nested functions.
  • Only call Hooks from React function components. Don't call Hooks from regular JavaScript functions.

What are we trying to achieve here?

There are no bounds to the functionality that you can achieve using Hooks. But, in this article, we are just creating a specific type of custom Hook to perform async operations (API calls in this example) and tailor it to fit our use-cases. We will also have a function fetchNow that can be used to fetch the data with a callback. This should be the basic API for our example Hook.

const { data, loading, error } = useFetch(
    "https://www.reddit.com/r/popular.json"
  );
Enter fullscreen mode Exit fullscreen mode

Alternative API could be the following.

const { data, loading, error, fetchNow } = useFetch();
Enter fullscreen mode Exit fullscreen mode

We will start with creating our Hook and we will name it useFetch. It takes url and options as parameters. We will use useState and useEffect Hooks internally to implement our Hook.

function useFetch(url: string, options?: any) {
  const [data, setData] = useState();
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState();

  function fetchNow(url: string, options?: any) {
    // we will add the code here
  }

  useEffect(() => {
      fetchNow(url, options);
  }, []);

  return { data, loading, error, fetchNow };
}
Enter fullscreen mode Exit fullscreen mode

To prevent the extra re-renders. We will merge our setState Hooks.

function useFetch(url: string, options?: any) {
  const [status, setStatus] = useState<{
    loading: boolean;
    error?: Error;
    data?: any;
  }>({
    loading: false
  });

  function fetchNow(url: string, options?: any) {
    // we will add the code here
  }

  useEffect(() => {
      fetchNow(url, options);
  }, []);

  return { ...status, fetchNow };
}
Enter fullscreen mode Exit fullscreen mode

Now, we have the bare-bones of our Hook ready. You can add the code according to the functionality of the Hook you are creating. In our case, we need to add the API calls. We will use the fetch API for this. After adding the logic, our function looks like this.

function useFetch(url?: string, options?: any) {
  const [status, setStatus] = useState<{
    loading: boolean;
    error?: Error;
    data?: any;
  }>({
    loading: false
  });
  function fetchNow(url: string, options?: any) {
    setStatus({ loading: true });
    fetch(url, options)
      .then((res: any) => res.json())
      .then((res: any) => {
        setStatus({ loading: false, data: res.data });
      })
      .catch((error: Error) => {
        setStatus({ loading: false, error });
      });
  }

  useEffect(() => {
    if (url) {
      fetchNow(url, options);
    }
  }, []);

  return { ...status, fetchNow };
}
Enter fullscreen mode Exit fullscreen mode

The function is complete now. We will use them in our functional component like the initially expected API or with a callback like in the code shown below. And we will get the fetched data status in the variables named data, loading, error.

<button onClick={() => fetchNow("https://www.reddit.com/r/popular.json")}>
  Fetch data
</button>
Enter fullscreen mode Exit fullscreen mode

TL;DR

You can check the sandbox below for the complete functionality of Hook.

What's next?

  • Cancelling Request: We can add the option to cancel the requests.
  • Caching: We can add a caching layer so that it does not have to make API calls for the same requests multiple times.

  • Central Error Handling: We can add an option to dispatch the error to a central handler in the project.

Conclusion

This is just one of the common use-cases of custom hooks. You can achieve a lot of great things with them. You got the idea on how to create custom Hooks. Here, we just made the API calls inside the Hook, but you can do all sorts of async operations using the same idea.

Thanks for reading and do give it a 💖 if you found it helpful!
Happy coding!

Top comments (4)

Collapse
 
hoverbaum profile image
Hendrik

Good stuff 👍

I think it's worth to point out that you can use the hook twice to fetch from multiple resources. By renaming the variables during destructuring of the hooks return you can even give your different resources speaking names instead of just data.

And by using generics you can even specify what type of data you are expecting to get back. typescriptlang.org/docs/handbook/g...

Collapse
 
scriptkavi profile image
ScriptKavi

Why create one when you can get all awesome hooks in a single library?

Try scriptkavi/hooks. Copy paste style and easy to integrate with its own CLI

Collapse
 
ankurkedia profile image
Ankur Kedia

Well, that's the idea. Not to have to use a library for every single function.

  • To know what's going on under the hood.
Collapse
 
scriptkavi profile image
ScriptKavi

It is not a installable library. Scriptkavi/hooks will create a file with the function directly in your project. Just like shadcn does. Try it. You will like it