Skip to content
react

How to use Async Await inside React's useEffect

Jul 3, 2022Abhishek EH4 Min Read
How to use Async Await inside React's useEffect

In one of my previous articles, I described how to use useEffect hook in react. In this article, we will see how to use async-await syntax inside useEffect.

Create a react app using the following command:

1npx create-react-app react-useeffect-async-await

Let's first fetch data from API using .then syntax:

App.js
1import { useEffect, useState } from "react"
2
3function App() {
4 const [imageUrl, setImageUrl] = useState()
5 useEffect(() => {
6 fetch("https://aws.random.cat/meow")
7 .then(response => {
8 return response.json()
9 })
10 .then(data => {
11 setImageUrl(data.file)
12 })
13 }, [])
14
15 return (
16 <div>
17 {imageUrl && (
18 <img src={imageUrl} alt="Random Cat" style={{ width: "300px" }} />
19 )}
20 </div>
21 )
22}
23
24export default App

In the above code, we are fetching a random cat image and displaying it.

Let's try converting it to async await syntax:

App.js
1import { useEffect, useState } from "react"
2
3function App() {
4 const [imageUrl, setImageUrl] = useState()
5 useEffect(async () => {
6 const response = await fetch("https://aws.random.cat/meow")
7 const data = await response.json()
8 setImageUrl(data.file)
9 }, [])
10
11 return (
12 <div>
13 {imageUrl && (
14 <img src={imageUrl} alt="Random Cat" style={{ width: "300px" }} />
15 )}
16 </div>
17 )
18}
19
20export default App

If you write the above code, you will get the following warning:

Effect callbacks are synchronous to prevent race conditions. Put the async function inside

useEffect vscode warning

If you ignore the warning and still run the application, you will get the following warning in the browser console:

Warning: useEffect must not return anything besides a function, which is used for clean-up.

useEffect browser warning

We receive this warning because react expects the first parameter of the useEffect to be a synchronous function. Here we are returning an asynchronous function.

We can fix this by having a self calling function:

App.js
1import { useEffect, useState } from "react"
2
3function App() {
4 const [imageUrl, setImageUrl] = useState()
5 useEffect(() => {
6 ;(async () => {
7 const response = await fetch("https://aws.random.cat/meow")
8 const data = await response.json()
9 setImageUrl(data.file)
10 })()
11 }, [])
12
13 return (
14 <div>
15 {imageUrl && (
16 <img src={imageUrl} alt="Random Cat" style={{ width: "300px" }} />
17 )}
18 </div>
19 )
20}
21
22export default App

If you run the app now the code should work fine.

The above syntax may look scary with so many brackets here and there. We can simplify this syntax using named functions.

App.js
1import { useEffect, useState } from "react"
2
3function App() {
4 const [imageUrl, setImageUrl] = useState()
5
6 useEffect(() => {
7 const fetchCatImage = async () => {
8 const response = await fetch("https://aws.random.cat/meow")
9 const data = await response.json()
10 setImageUrl(data.file)
11 }
12 fetchCatImage()
13 }, [])
14
15 return (
16 <div>
17 {imageUrl && (
18 <img src={imageUrl} alt="Random Cat" style={{ width: "300px" }} />
19 )}
20 </div>
21 )
22}
23
24export default App

Here we have put all the async calls inside the fetchCatImage function and called it inside the useEffect.

If you need to declare the fetchCatImage function outside the useEffect, you can do so as shown below:

App.js
1import { useEffect, useState } from "react"
2
3function App() {
4 const [imageUrl, setImageUrl] = useState()
5
6 const fetchCatImage = async () => {
7 const response = await fetch("https://aws.random.cat/meow")
8 const data = await response.json()
9 setImageUrl(data.file)
10 }
11
12 useEffect(() => {
13 fetchCatImage()
14 }, [fetchCatImage])
15
16 return (
17 <div>
18 {imageUrl && (
19 <img src={imageUrl} alt="Random Cat" style={{ width: "300px" }} />
20 )}
21 </div>
22 )
23}
24
25export default App

The above code will work. However, you will receive a warning:

The 'fetchCatImage' function makes the dependencies of useEffect Hook (at line 14) change on every render. Move it inside the useEffect callback. Alternatively, wrap the definition of 'fetchCatImage' in its own useCallback() Hook.

This warning is shown because, in the above code, fetchCatImage will be declared every time the component re-renders. Since we have given fetchCatImage as a dependency for useEffect, the useEffect hook will run in each re-render, which is not what we want.

We can prevent this by wrapping fetchCatImage inside a useCallback hooks as shown below:

App.js
1import { useCallback, useEffect, useState } from "react"
2
3function App() {
4 const [imageUrl, setImageUrl] = useState()
5
6 const fetchCatImage = useCallback(async () => {
7 const response = await fetch("https://aws.random.cat/meow")
8 const data = await response.json()
9 setImageUrl(data.file)
10 }, [])
11
12 useEffect(() => {
13 fetchCatImage()
14 }, [fetchCatImage])
15
16 return (
17 <div>
18 {imageUrl && (
19 <img src={imageUrl} alt="Random Cat" style={{ width: "300px" }} />
20 )}
21 </div>
22 )
23}
24
25export default App

It is always a best practice to wrap await calls inside try catch blocks as shown below:

1useEffect(() => {
2 const fetchCatImage = async () => {
3 try {
4 const response = await fetch("https://aws.random.cat/meow")
5 const data = await response.json()
6 setImageUrl(data.file)
7 } catch (error) {
8 console.error("something went wrong")
9 // Handle error
10 }
11 }
12 fetchCatImage()
13}, [])

You can view the complete source code here.

Do follow me on twitter where I post developer insights more often!

© 2024 CodingDeft.Com