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:
1import { useEffect, useState } from "react"23function 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 }, [])1415 return (16 <div>17 {imageUrl && (18 <img src={imageUrl} alt="Random Cat" style={{ width: "300px" }} />19 )}20 </div>21 )22}2324export 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:
1import { useEffect, useState } from "react"23function 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 }, [])1011 return (12 <div>13 {imageUrl && (14 <img src={imageUrl} alt="Random Cat" style={{ width: "300px" }} />15 )}16 </div>17 )18}1920export 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
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.
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:
1import { useEffect, useState } from "react"23function 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 }, [])1213 return (14 <div>15 {imageUrl && (16 <img src={imageUrl} alt="Random Cat" style={{ width: "300px" }} />17 )}18 </div>19 )20}2122export 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.
1import { useEffect, useState } from "react"23function App() {4 const [imageUrl, setImageUrl] = useState()56 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 }, [])1415 return (16 <div>17 {imageUrl && (18 <img src={imageUrl} alt="Random Cat" style={{ width: "300px" }} />19 )}20 </div>21 )22}2324export 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:
1import { useEffect, useState } from "react"23function App() {4 const [imageUrl, setImageUrl] = useState()56 const fetchCatImage = async () => {7 const response = await fetch("https://aws.random.cat/meow")8 const data = await response.json()9 setImageUrl(data.file)10 }1112 useEffect(() => {13 fetchCatImage()14 }, [fetchCatImage])1516 return (17 <div>18 {imageUrl && (19 <img src={imageUrl} alt="Random Cat" style={{ width: "300px" }} />20 )}21 </div>22 )23}2425export 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:
1import { useCallback, useEffect, useState } from "react"23function App() {4 const [imageUrl, setImageUrl] = useState()56 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 }, [])1112 useEffect(() => {13 fetchCatImage()14 }, [fetchCatImage])1516 return (17 <div>18 {imageUrl && (19 <img src={imageUrl} alt="Random Cat" style={{ width: "300px" }} />20 )}21 </div>22 )23}2425export 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 error10 }11 }12 fetchCatImage()13}, [])
You can view the complete source code here.
Do follow me on twitter where I post developer insights more often!