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.
Atlassian Tools Expert | eazyBI & ScriptRunner Specialist | Forge Developer | Streamlining Workflows & Delivering Business Solutions
6 个月Good Explanation, this actually helped.
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.