Async Effect Gotcha
Suppose we want to fetch some data on mount, and we don't want to use SWR. We'll make a fetch
request inside a useEffect
hook.
Using the async/await syntax 👀, most developers would try to do something like this:
React.useEffect(async () => { const response = await fetch(ENDPOINT); const json = await response.json();
setTemperature(json.temperature);}, []);
In order to use the await
keyword, we need to be within an async
function. It seems logical that we'd make our effect callback async, but unfortunately, it doesn't work:
Code Playground
Something went wrong
destroy is not a function
Lint Warning
Effect callbacks are synchronous to prevent race conditions. Put the async function inside: useEffect(() => { async function fetchData() { // You can await here const response = await MyAPI.getData(someId); // ... } fetchData(); }, [someId]); // Or [] if effect doesn't need props or state Learn more about data fetching with Hooks: https://fb.me/react-hooks-data-fetching
Rule: react-hooks/exhaustive-deps
Location: Line 8, Column 19
- destroy is not a function
- Warning: useEffect must not return anything besides a function, which is used for clean-up. It looks like you wrote useEffect(async () => ...) or returned a Promise. Instead, write the async function inside your effect and call it immediately: useEffect(() => { async function fetchData() { // You can await here const response = await MyAPI.getData(someId); // ... } fetchData(); }, [someId]); // Or [] if effect doesn't need props or state Learn more about data fetching with Hooks: https://reactjs.org/link/hooks-data-fetching at App (https://sandpack-bundler.vercel.app/App.js:24:42)
- Warning: useEffect must not return anything besides a function, which is used for clean-up. It looks like you wrote useEffect(async () => ...) or returned a Promise. Instead, write the async function inside your effect and call it immediately: useEffect(() => { async function fetchData() { // You can await here const response = await MyAPI.getData(someId); // ... } fetchData(); }, [someId]); // Or [] if effect doesn't need props or state Learn more about data fetching with Hooks: https://reactjs.org/link/hooks-data-fetching
- The above error occurred in the <App> component: at App (https://sandpack-bundler.vercel.app/App.js:24:42) Consider adding an error boundary to your tree to customize error handling behavior. Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.
- Could not consume error:[[Error]]
- destroy is not a function
- The above error occurred in the <App> component: at App (https://sandpack-bundler.vercel.app/App.js:24:42) Consider adding an error boundary to your tree to customize error handling behavior. Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.
- Could not consume error:[[Error]]
- Could not consume error:[[Error]]
In addition to the cryptic error message, we also see the following warning in the console:
Warning: useEffect must not return anything besides a function, which is used for clean-up.
It looks like you wrote useEffect(async () => ...) or returned a Promise. Instead, write the async function inside your effect and call it immediately.
We're not allowed to make the effect callback async. Instead, we need to create another function within the effect. Here's how I typically solve this problem:
React.useEffect(() => { // Create an async function within our effect: async function runEffect() { const response = await fetch(ENDPOINT); const json = await response.json();
setTemperature(json.temperature); }
// Immediately call this function to run the effect: runEffect();}, []);
Essentially, we move all of our effect logic into this async function. I like to use a generic name like runEffect
instead of a specific name like fetchTemperature
, to make it clear that we're moving everything related to the effect into this function.
If our effect has a cleanup function, that cleanup function should not be included in our runEffect
function:
React.useEffect(() => { async function runEffect() { // ... Effect logic here }
runEffect();
return () => { // ... Cleanup logic here }}, []);
To be clear: we don't run into this issue at all when we use a data-fetching library like SWR. This is one of many problems that those tools solve for us!
But I wanted to cover this anyway because not everyone uses data-fetching libraries. And even if you do, there may still be cases where you want to await
some sort of async operation unrelated to data fetching.