How to Use Async/Await in the React useEffect() Hook
How to Use Async/Await in the React useEffect() Hook

How to Use Async/Await in the React useEffect() Hook

To?await?an?async?function in the React?useEffect()?hook, wrap the?async?function in an immediately invoked function expression (IIFE).

For example:

const [books, setBooks] = useState([]);

useEffect(() => {
  (async () => {
    try {
      // await async "fetchBooks()" function
      const books = await fetchBooks();
      setBooks(books);
    } catch (err) {
      console.log('Error occured when fetching books');
    }
  })();
}, []);        

In this article, we’ll look at different ways to call an?async?function in the?useEffect()?hook, along with pitfalls to avoid when working with?async/await.

Calling async Functions With then/catch in useEffect()

async?functions perform an asynchronous operation in JavaScript. To wait for the?Promise?the?async?function returns to be settled (fulfilled or rejected) in the React useEffect() hook, we could use its?then()?and?catch()?methods:


In the following example, we call the?fetchBooks()?async method to fetch and display stored books in a sample reading app:

export default function App() {
  const [books, setBooks] = useState([]);
  useEffect(() => {
    // await async "fetchBooks()" function
    fetchBooks()
      .then((books) => {
        setBooks(books);
      })
      .catch(() => {
        console.log('Error occured when fetching books');
      });
  }, []);
  return (
    <div>
      {books.map((book) => (
        <div>
          <h2>{book.title}</h2>
        </div>
      ))}
    </div>
  );
}        

async/await Problem: async Callbacks Can’t Be Passed to useEffect()

Perhaps you would prefer to use the?async/await?syntax in place of?then/catch. You might try doing this by making the callback passed to?useEffect()?async.


This isn’t a good idea though, and if you’re using a linter it will inform you of this right away.

// ? Your linter: don't do this!
useEffect(async () => {
  try {
    const books = await fetchBooks();
    setBooks(books);
  } catch {
    console.log('Error occured when fetching books');
  }
}, []);        

Your linter complains because the first argument of?useEffect()?is supposed to be a function that either returns nothing or returns a function to clean up side effects. But?async?functions always return a?Promise?(implicitly or explicitly), and?Promise?objects can't be called as functions. This could cause real issues in your React app, such as memory leaks.

useEffect(async () => {
  const observer = () => {
    // do stuff
  };
  await fetchData();
  observable.subscribe(observer);
  // Memory leak!
  return () => {
    observable.unsubscribe(observer);
  };
}, []);        

In this example, because the callback function is?async, it doesn't actually return the defined clean-up function, but rather a?Promise?object that is resolved with the clean-up function. Hence, this clean-up function is never called, and the observer is never unsubscribed from the observable, resulting in a memory leak.

So how can we fix this? How can we use the?await?operator with an?async?function in the?useEffect()?hook?

async/await Solution 1: Call async Function in IIFE

One straightforward way to solve this problem is to?await?the?async?function in an?immediately invoked function expression?(IIFE):


const [books, setBooks] = useState([]);

useEffect(() => {
  (async () => {
    try {
      const books = await fetchBooks();
      setBooks(books);
    } catch (err) {
      console.log('Error occured when fetching books');
    }
  })();
}, []);        

As the name suggests, an IIFE is a function that runs as soon as it is defined. They are used to avoid polluting the global namespace and in scenarios where trying an?await?call could cause problems in the scope containing the IIFE (e.g., in the?useEffect()?hook).

async/await Solution 2: Call async Function in Named Function

Alternatively, you can?await?the?async?function inside a named function:


useEffect(() => {

  // Named function "getBooks"
  async function getBooks() {
    try {
      const books = await fetchBooks();
      setBooks(books);
    } catch (err) {
      console.log('Error occured when fetching books');
    }
  }
  // Call named function
  getBooks();
}, []);        

Remember the example using the observable pattern? Here’s how we can use a named?async?function to prevent the memory leak that occurred:

// ? callback is not async
useEffect(() => {
  const observer = () => {
    // do stuff
  };
  // Named function "fetchDataAndSubscribe"
  async function fetchDataAndSubscribe() {
    await fetchData();
    observable.subscribe(observer);
  }
  fetchDataAndSubscribe();
  // ? No memory leak
  return () => {
    observable.unsubscribe(observer);
  };
}, []);        

async/await Solution 3: Create Custom Hook

We can also create a custom hook that behaves similarly to?useEffect()?and can accept an?async?callback without causing any issues.


The custom hook could be defined this way:

export function useEffectAsync(effect, inputs) {
  useEffect(() => {
    return effect();
  }, inputs);
}        

And we’ll be able to call it from multiple places in our code like this:

const [books, setBooks] = useState([]);

useEffectAsync(async () => {
  try {
    const books = await fetchBooks();
    setBooks(books);
  } catch (err) {
    console.log('Error occured when fetching books');
  }
});        

With these three approaches, we can now easily use the?await?operator with?async?functions in the?useEffect()?hook.

Sambhav Swain

Atlassian Tools Expert | eazyBI & ScriptRunner Specialist | Forge Developer | Streamlining Workflows & Delivering Business Solutions

6 个月

Good Explanation, this actually helped.

回复
Bhuvnesh Kumar Sharma

Full-Stack Web developer, NodeJs || ReactJs || ExpressJs || Go || SQL || NOSQL || Git || HTML || CSS ||. Javascript

1 年

nicely explained, i was facing same issue and it forced me to use .then .then method of promise.

要查看或添加评论,请登录

Hasibul Islam的更多文章

社区洞察

其他会员也浏览了