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>
)
}
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.
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(){
}
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])
}
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 }
}
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>}
)
}
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 (29)
If the endpoint just returns a JSON, you can simplify it quite a bit by just using
fetch
and not usingasync
/await
:Cheers!
That's not any simpler... It's just using the old Promise api...
Do you objectively believe that? I mean, IDK about you, but this:
Is unnecessarily more complex than this:
async
/await
+try
/catch
makes sense sometimes in really complex promise chains that depend on each other, but having something as simple asfetch
, they don't make much sense :/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...
I love how you keep using "old" as an argument. Using
async
/await
instead ofPromises
is not the same as usingclass
instead ofprototype
. When writing a "class", is always simpler to just useclass
instead ofprototype
, so in that case "old is worst", yes.But in the case of
async
/await
, in some scenarios the new approach is way more complex. You shouldn't use "one or the other", you should just use the one that makes the code easier to read. For simple stuff likefetch
, the "new" approach makes it harder to read:We should try to avoid using stuff just because is "new" and being like this...

And instead use the new features where they are useful. For
async
/await
that's when the promise chain becomes complex (promises that depend on other promises, async iterables and so on).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: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.
And Finally!
I use axios
Try redaxios instead. Pretty much the same API, but closer to
fetch
and maintained by the same folks behind preact.Good stuff. I like to use a custom hook I made called useStateAndLocalStorage. I love the self documenting nature of custom hooks.
Please I'd like to see how that works. 🤔
Maybe I'll like it too
Check out my blog on this James.
dev.to/imrishabh18/simplest-custom...
Thanks!
That's pretty close to what I made.
A good thing to note about this is that using custom hooks and hooks inside a useEffect causes an infinite loop.
Not quite. If he has only
url
as a dependency of that effect, the effect will only run if that value changes.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.
Yup, you can configure ESLint with eslint-plugin-react-hooks to do just that, but it throws a warning mainly because you might want to just depend on some of the things inside the effect, not all of them. Still, the exhaustive-deps rule is smart enough to know that it doesn't have to re-run with state setters from
useState
.There is also the
useCallback
hook which can be used to create a memoized function that, when passed touseEffect
dependency array, doesn't triger re-renders.Yup, but ideally that should be in the consumer side of the hook, not in its internals.
I'm not sure I understand what you mean, can you elaborate?
Ideally, you shouldn’t memoize a callback set by the consumer of your hook. If you memoize it on your side (inside your hook) with
useCallback
, then if the consumer of your hook changes the callback, your hook will still be using the old one. IdeallyuseCallback
should be used by the consumer itself, so: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.
You should also check:
kent.dev/blog/stop-using-isloading...
Sure Manuel. Thank you for the article.
Updated link: kentcdodds.com/blog/stop-using-isl...
How would you handle different http methods? Here you have 1 hook using only get().
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.
How fetch again after submitting any form if need in some case?