useEffect.dev
← Back to lessons

Lesson 2. Trigger side effects with useEffect 4. Async functions

The function passed to useEffect can only return one thing: another function called when the component is unmounted. It isn’t possible to return anything else.

It doesn’t look like a big constraint, but it means that the following code is invalid:

useEffect(async () => {
const res = await fetch('http://some.api')
const data = await res.json()
setData(data)
}, [])

If you execute it, React will give you an error, telling you that you returned something that isn’t a function, a promise in this case. This is because this code with an async is equivalent to:

useEffect(() => {
return fetch('http://some.api')
.then((res) => res.json())
.then((data) => setData(data))
}, [])

And unfortunately it is forbidden to return a promise. This is sad, because async functions are very nice to use. The good news is that some tricks will make you able to use such functions, and make React happy:

useEffect(() => {
// First declare the async function
const fetchData = async () => {
const res = await fetch('http://some.api')
const data = await res.json()
setData(data)
}
// Then call it (without `await`)
fetchData()
})

You can even declare the async function and call it immediately:

useEffect(() => {
;(async () => {
const res = await fetch('http://some.api')
const data = await res.json()
setData(data)
})()
})

Be careful if you need to return a clean-up function. The function passed to useEffect must return it, not the async function you declare inside. If you use a flag in this clean-up function, you may have to declare it outside of the async function:

useEffect(() => {
let active = true
;(async () => {
const res = await fetch('http://some.api')
const data = await res.json()
if (active) {
setData(data)
}
})()
return () => (active = false)
})