Custom Hooks in React: useFetch

Welcome to my blog ππ½
I'm a Front-End Developer with a passion for learning!
I write about Programming π©π½βπ» and Productivity Tips β
Search for a command to run...

Welcome to my blog ππ½
I'm a Front-End Developer with a passion for learning!
I write about Programming π©π½βπ» and Productivity Tips β
Thank you so much for this! I was really struggling with calling the custom hook at a later time. I kinda hit a dead end and thought I might better off writing the entire thing inside my component, but thanks to you.
Now my hook resides in a separate component, nice and neat.
Thanks for leaving a comment. Iβm happy to hear that the article was helpful! π
In this series, you will find custom React hooks for solving common coding challenges.
The addition of hooks in React 16.8 added massive improvements and made it easier than ever to reuse functionality between components. There are pre-defined hooks like useState and useEffect but, there is also the ability to create custom hooks to su...
Although my experience is primarily with front-end development, my current job is affording me the opportunity to do more full-stack work. Naturally, this meant that I needed to explore the different database GUIs available on the market. Here is a l...

A Note on the Series: Fast Fridays π is a series where you will find fast, short, and sweet tips/hacks that you may or may not be aware of. I will try to provide these (fairly) regularly on Fridays. In this Fast Friday tip, I'll explain how to crea...

Over the past year, Iβve started incorporating a Git GUI into my workflow. I still reach for the terminal about 90% of the time, but Iβve found that a GUI can really shine when it comes to things like viewing diffs, checking stashes, or resolving mer...

Simplifying Branch Management with Git Worktree

With all the AI tools popping up these days, I figured I'd try out some speech-to-text software in an attempt to boost my productivity. I've been hearing a lot about βdeveloping at the speed of thoughtβ, so here's a list of the top speech-to-text sof...

Retrieving data from an API is an extremely common task for a developer. Because this is such a frequent operation to complete, it's beneficial to abstract the internals and boilerplate of this functionality into a reusable hook.
In this tutorial, you'll learn how to create a hook, useFetch, that can help with grabbing data using the native Fetch API. For more information on what custom hooks are, check out my first article on creating a useMediaQuery hook or read the official React docs.
The Fetch API is a native web API that provides an interface for asynchronously fetching resources. There is a fetch() method that allows you to send a network request and get information from the server. The fetch() method requires one mandatory argument which represents the URL of the resource you would like to obtain data from. It returns a Promise that resolves to the Response of that request. Additionally, you can pass in a second argument, options, that contains an object with custom settings that you may want to apply to the request.
This is a relatively high-level overview of the Fetch API, so if you'd like to take a deeper dive, make sure to check out the docs on MDN.
π I've created a Github Gist that contains the code for the hook I am about to show you how to implement, so feel free to check that out if you want to reference it later.
To understand the internals of the hook, let's first take a look at the finished code and then break it down.
import { useEffect, useState } from "react";
const useFetch = (initialUrl, options) => {
const [url, setUrl] = useState(initialUrl || "")
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
if(!url) return
const fetchData = async () => {
setLoading(true)
try {
const response = await fetch(url, options)
if (!response.ok) {
throw new Error(`HTTP error status: ${response.status}`);
}
const json = await response.json()
setData(json)
setLoading(false)
setError(null)
} catch (error) {
//This catches the error if either of the promises fails or the manual error is thrown
setLoading(false)
setError(error.message)
}
}
fetchData()
}, [url, options]);
return [{ data, loading, error }, setUrl];
}
export default useFetch;
useFetch function that accepts two optional parameters: initialUrl - This defines the URL of the resource that you wish to fetch. This is required if you wish to make an API call when the component is first rendered.options - An object containing any custom settings that you want to apply to the request. (Check out the parameters section within the MDN docs to see an exhaustive list of available fields that can be passed into this object)url - The URL of the resource that we want to make the fetch call on.data - The data structure containing the result of the fetch call after it completes.loading - Indicates whether the async action is in progress or not.error - If the fetch operation produces an error, this will contain the error message associated with the failed operation.useEffect hook that will handle all the side effects when the url or options arguments change.fetchData() function is invoked, and goes through the following process:loading state is set to true to indicate that a request is in flight. const response = await fetch(url, options). true then we manually throw an error which will trigger the catch block.response.okfetch() promise only rejects when a network error is encountered (which is usually when there's a permissions issue or similar). A fetch() promise does not reject on HTTP errors (404, etc.). Instead, a separate conditional must check the Response.ok and/or Response.status properties.
const json = await response.json(). data, loading, and error states are updated accordingly to reflect this output.data, loading, and error fields. The second index includes the setUrl function which provides the ability to dynamically set the URL. (More on why the setter function is being passed back in the next section)There are two separate ways that we can leverage our custom useFetch hook. The first method involves making an API call when the component is first rendered. This involves passing in the URL to the useFetch hook when the hook is first instantiated.
import React, { useState } from "react";
import useFetch from "./hooks/useFetch";
function App() {
const [selectedBreed, setSelectedBreed] = useState("");
const [{ data: breeds, loading: loadingBreeds }] = useFetch("https://dog.ceo/api/breeds/list/all")
//....Rest of App code
}
useFetch hook is declared at the top of the component and passes in the initial URL to fetch from. The data and loading fields are being destructured out of the array returned back from the hook. In addition, object destructuring and renaming are being used to create the breeds and loadingBreeds to provide more explicit state names.The second method involves fetching in response to events outside of the component render. This approach involves using the setter function to invoke the fetch call in a more dynamic fashion.
This implementation is emulated after Apollo Client's -
useLazyQueryhook.
In the previous section, we saw that the hook returns back a setUrl setter function. Because we have to adhere to the rules of hooks, we cannot call hooks inside nested functions so the setter function provides a way to make the fetch process a bit more dynamic.
import React, { useState } from "react";
import useFetch from "./hooks/useFetch";
function App() {
const [selectedBreed, setSelectedBreed] = useState("");
const [{ data: breeds, loading: loadingBreeds }] = useFetch("https://dog.ceo/api/breeds/list/all")
const [{ data: dogData, loading: loadingImages }, fetchImages] = useFetch()
const searchByBreed = () => {
fetchImages(`https://dog.ceo/api/breed/${selectedBreed}/images`)
};
//....
<button
type="button"
disabled={!selectedBreed}
onClick={searchByBreed}
>
Search
</button>
//....Rest of App code
const [{ data: dogData, loading: loadingImages }, fetchImages] = useFetch() which destructures out the object housing the state variables and the setter function which we have relabeled as fetchImages.useFetch hook as we did in the first example. This is intentional as the URL is being set in the searchByBreed click handler with the fetchImages() setter function. useFetch hook in this way makes it so we can run a query in response to different events, like the click of a button in this example, while adhering to the rules of hooks.Although this useFetch implementation is not the most robust solution out in the wild, this is a great option for projects where you want to get up and running quickly without a third-party library. Plus, it solves most use cases when grabbing data for side projects.
For alternative data fetching solutions, check out popular libraries like SWR, React Query, and, if using GraphQL, Apollo Client's useQuery hook.
If you enjoy what you read, feel free to like this article or subscribe to my newsletter, where I write about programming and productivity tips.
As always, thank you for reading, and happy coding!