DEV Community

Cover image for 💡 React Hooks: async function in the useEffect
Danial Dezfouli
Danial Dezfouli

Posted on • Updated on

💡 React Hooks: async function in the useEffect

When you're new to React Hooks, you may notice that you get warnings and bugs if you use an async function inside the useEffect Hook. Let's find out why this happens.

There are dozens of articles and issues about how to use async in the React Hooks:

Why is this happening?

Async functions always return a promise so you will not have the actual value until the Promise is fulfilled.

Anti-Pattern: async function directly in the useEffect

React can run this async function but can not run the cleanup function.
Don't use raw async function directly in the useEffect.

useEffect(async () => {
  console.log('Hi :)')

  return () => {
    console.info('Bye!') // It won't run
  };
}, []);
Enter fullscreen mode Exit fullscreen mode

Code example: using unmount in async functions.

You don't have to unmount callback unless you use await expression before it.

unmount = await (async () => {
  console.log('Hi :)')

  return () => {
    console.info('Bye!')
  };
})()
unmount()
// Hi :)
// Bye!
Enter fullscreen mode Exit fullscreen mode

Code example: using unmount in a function.

unmount = (() => {
  console.log('Hi :)')

  return () => {
    console.info('Bye!') // 👍 
  };
})()
unmount()
// Hi :)
// Bye!
Enter fullscreen mode Exit fullscreen mode

Code example: using async function in the useEffect.

You can create an async function in the useEffect callback, as Nick mentioned in his article.

  useEffect(() => {
    (async () => {
      const products = await api.index()
      setFilteredProducts(products)
      setProducts(products)
    })()

    return () => {
      unsubscribeOrRemoveEventHandler() // 👍 
    }
  }, [])
Enter fullscreen mode Exit fullscreen mode

I hope you find this article useful.

Discussion (19)

Collapse
valeriavg profile image
Valeria

As it was already mentioned in the comments, having raw async functions in the useEffect is always a bad idea. Once created, the promise cannot be stopped, it will inevitably resolve or fail, even if the component itself is long gone.
In other words, one needs to make sure that when promise results are evaluated the mounted state is taken into consideration or that errors are properly silenced

Collapse
raibtoffoletto profile image
Raí B. Toffoletto

Exactly... and then the console yells at you that there's a state changed in a unmounted component and that's a memory leak bug in the app.

After sometime experimenting with api call in useEffect at work we got to the conclusion that any async logic state goes to Redux. Better to have a more complex and organized store and leave components with simple logic.

Collapse
danialdezfouli profile image
Danial Dezfouli Author

As Samuel mentioned, we can check if the component is unmounted or not to update the state.

Thread Thread
icyjoseph profile image
Joseph

Not really... You shouldn't really be doing handcrafted isMounted checks at all. To prevent state updates from an unstoppable promise/async function, use a ref on an HTML element on whatever you are rendering on this component, if the ref.current value is null then throw in the promise or exit the async function gracefully, skipping any updates.

Of course for fetch/axios there's a proper abort/cancel mechanism.

Collapse
digitalbrainjs profile image
Dmitriy Mozgovoy

having raw async functions in the useEffect is always a bad idea
Once created, the promise cannot be stopped

Unless you're using async effects with cancelable promises :)

Collapse
elmehdiyessad profile image
elmehdi

Hey in order to get data once and prevent useEffect hook that render the data in the infinite loop you should just initialize an empty array as a second argument to the useEffect hook like this

useEffect(() => {
// use code here
}, [])

Collapse
lukeshiru profile image
Luke Shiru

Thanks for making clear since the beginning of the article that async in effects is an anti-pattern. Generally if you're in a situation in which you need async effects, you might need to think that again. You should be avoiding side-effects altogether, but if you really need them, having them as async functions is not ideal because they might produce "async side effects" and good ol' leaks.

Collapse
rrsai profile image
Roy Ronalds

??? What is then the alternative? I mean, we're reaching out to an api over the network, as is necessary.

Collapse
lukeshiru profile image
Luke Shiru

You should avoid doing that, actually. I recently wrote a comment in a similar article here ... but is discouraged to use effects to fetch data, and even more so with async/await. Luckily this will be far more clear to everyone when the official beta docs of react become the stable docs 😅

Thread Thread
rrsai profile image
Roy Ronalds

You mention two npm packages, but pulling in two extra npm packages just to get data from an api (something every page will require on average) is not a good "solution". And if those are the react external docs for using an api within useEffect, a lot of people are going to be googling elsewhere for solutions, as it's not even mentioned within the first ten pages of text. And explicitly says that APIs are one of the use cases of useEffect, then doesn't exaplain how to interact with an api in the guide itself.

At it's base, let's say you have fetch & React available, not two custom developed packages from the npm ecosystem. You then need to load in api data from the results of a promise, and using async/await should be the first step... ...which causes problems with useEffect.

Catch-22. What a mess.

Thread Thread
lukeshiru profile image
Luke Shiru

The thing is, we shouldn't be using useEffect for fetching. For example, the React 18 StrictMode runs effects twice in development intentionally to break your app if you're relying on effects for stuff that you shouldn’t, so you fix it in dev.

The issue with effects loading data is that you're effectively making a waterfall of requests instead of running them in parallel, mainly because components don't start fetching until they are "mounted" which is far from ideal.

The point here is not that you "can't" fetch in effects, is more that you "shoudln't". Ideally we should be avoiding useEffect as much as possible. Even for cases when it was actually useful, it will soon be replaced with the new useEvent.

Historically the main issue was that folks thought that useEffect was just another way of doing lifecycle methods, but actually we needed a "paradigm shift" and stop thinking in the lifecycle of the component ("when this component is mounted..." and mental models like that), and start thinking it more as isolated stateless components.

Collapse
samthecodingman profile image
Samuel Jones

A simplistic way to dump the result would be to track whether the useEffect's unsubscribe function has been called using:

useEffect(() => {
  let disposed = false

  (async () => {
    const products = await api.index()

    if (disposed) return

    setFilteredProducts(products)
    setProducts(products)
  })()

  return () => disposed = true
}, [])
Enter fullscreen mode Exit fullscreen mode

But as others have said, Redux is often the way to go if you are doing this often and there are also the use-async-effect and @react-hook/async packages for useAsyncEffect implementations.

Collapse
john1625b profile image
john1625b

I'm still getting the error

ESLint: Promises must be handled appropriately or explicitly marked as ignored with the `void` operator.(@typescript-eslint/no-floating-promises)

on the async word on the second line

Collapse
danialdezfouli profile image
Danial Dezfouli Author

Can you share your code?

Collapse
apperside profile image
Apperside

I think there is an error in the following sentence:

Async functions always return a promise so you will have the actual value until the Promise is fulfilled

It should be

Async functions always return a promise so you will NOT have the actual value until the Promise is fulfilled

Collapse
danialdezfouli profile image
Danial Dezfouli Author

Thank you, I made a grammatical error

Collapse
symaticvisuals profile image
Deepanshu Goel

Hey, I didn't understand the concept thoroughly. Can you explain a little bit more.

Collapse
danialdezfouli profile image
Danial Dezfouli Author • Edited on

Some developers tried to use async functions in useEffect and got warnings/bugs with the following code:

Anti-Pattern: async function directly in the Hook

useEffect(async () => {
await fetch(EXAMPLE_URL)
}, []);
Enter fullscreen mode Exit fullscreen mode

I have explained the reason why eslint shows warnings and our cleanup function will not run (because of the async function).

Async functions have to be written in the callback as follows:

useEffect(() => {
    (async () => {
      const products = await api.index()
      setProducts(products)
    })()
  }, [])
Enter fullscreen mode Exit fullscreen mode
Collapse
amn3s1a2018 profile image
Amn3s1a2018

There is an useAsyncEffect custom hook. (Out there in some npm package) The cleanup function is passed in a separate parameter, so it doesn't share the full scope of the effect function, like it does in the original useEffect hook.
You may write your own if that option is more pleasant.