Note: If you want to support SSR/SSG for SEO, use framework specific api from React-router/Remix, Next.js or Gatsby.

For React version >=19

We now have nice loader and form action api:

Loading data:

function Comments({ dataPromise }) {
  const data = use(dataPromise);
  return <div>{JSON.stringify(data)}</div>;
}

async function loadData() {
  return { data: "some data" };
}

export function Index() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Comments dataPromise={loadData()} />
    </Suspense>
  );
}

Form actions

async function updateDataAndLoadNew() {
  // update data
  // load new data

  // return new data
  return { data: "some data" };
}

export default function Index() {
  const [state, action, isPending] = useActionState(
    async (prev, formData) => {
      return updateUserAndLoadNewData();
    },
    { data: "initial state, no data" } // can be null
  );

  if (state.error) {
    return `Error ${state.error.message}`;
  }

  return (
    <form action={action}>
      <input type="text" name="name" />
      <button type="submit" disabled={isPending}>
        Do it
      </button>
      {state.data && <p>Data {state.data}</p>}
    </form>
  );
}

For React version >=18

Starting with React 18 you can also use Suspense, but it's not yet recommended if you are not using frameworks that correctly implement it:

In React 18, you can start using Suspense for data fetching in opinionated frameworks like Relay, Next.js, Hydrogen, or Remix. Ad hoc data fetching with Suspense is technically possible, but still not recommended as a general strategy.

If not part of the framework, you can try some libs that implement it like swr.


Oversimplified example of how suspense works. You need to throw a promise for Suspense to catch it, show fallback component first and render Main component when promise it's resolved.

let fullfilled = false;
let promise;

const fetchData = () => {
  if (!fullfilled) {
    if (!promise) {
      promise = new Promise(async (resolve) => {
        const res = await fetch('api/data')
        const data = await res.json()

        fullfilled = true
        resolve(data)
      });
    }

    throw promise
  }
};

const Main = () => {
  fetchData();
  return <div>Loaded</div>;
};

const App = () => (
  <Suspense fallback={"Loading..."}>
    <Main />
  </Suspense>
);

For React version <=17

I suggest to look at Dan Abramov (one of the React core maintainers) answer here:

I think you're making it more complicated than it needs to be.

function Example() {
  const [data, dataSet] = useState<any>(null)

  useEffect(() => {
    async function fetchMyAPI() {
      let response = await fetch('api/data')
      response = await response.json()
      dataSet(response)
    }

    fetchMyAPI()
  }, [])

  return <div>{JSON.stringify(data)}</div>
}

Longer term we'll discourage this pattern because it encourages race conditions. Such as — anything could happen between your call starts and ends, and you could have gotten new props. Instead, we'll recommend Suspense for data fetching which will look more like

const response = MyAPIResource.read();

and no effects. But in the meantime you can move the async stuff to a separate function and call it.

You can read more about experimental suspense here.


If you want to use functions outside with eslint.

 function OutsideUsageExample({ userId }) {
  const [data, dataSet] = useState<any>(null)

  const fetchMyAPI = useCallback(async () => {
    let response = await fetch('api/data/' + userId)
    response = await response.json()
    dataSet(response)
  }, [userId]) // if userId changes, useEffect will run again

  useEffect(() => {
    fetchMyAPI()
  }, [fetchMyAPI])

  return (
    <div>
      <div>data: {JSON.stringify(data)}</div>
      <div>
        <button onClick={fetchMyAPI}>manual fetch</button>
      </div>
    </div>
  )
}
Answer from ZiiMakc on Stack Overflow
🌐
Reddit
reddit.com › r/react › useeffect vs async/await
r/react on Reddit: useEffect vs async/await
September 15, 2023 -

New to react.

So, I wrote some code and needed to refactor out to hooks. Couldn’t get the hook to work until I realized my

 const useMyHook = async () => {
      useEffect(()=>{
           calling API stuff
      }, [])
      return {blah, blah, blah}
 }

had async wrapped around useEffect not inside. Vexed me for a few days. Sat down this morning and realized that pattern was maybe not correct. Took out that async and it works.

Forgive my potential syntax above.

So I’m trying to make sure I understand useEffect with this.

useEffect is not a replacement for async, but seems to resolve just fine without any async/await internals for an API call. Why is this and will I run into issues with longer wait times for responses?

Is best practice to use async functions inside of useEffect and then call those functions within the same useEffect with await then return? If not, what is the best practice?

I probably also need more info about useEffect. I find the docs kinda split between too low level technical and too high level.

My understanding so far is that it is a built in hook intended to be used when accessing external services. But I don’t really know why besides the convenience of monitoring some state and firing when it changes. Especially not sure why if you still have to implement async functions inside of it. Not sure what exactly useEffect provides.

Top answer
1 of 16
863

Note: If you want to support SSR/SSG for SEO, use framework specific api from React-router/Remix, Next.js or Gatsby.

For React version >=19

We now have nice loader and form action api:

Loading data:

function Comments({ dataPromise }) {
  const data = use(dataPromise);
  return <div>{JSON.stringify(data)}</div>;
}

async function loadData() {
  return { data: "some data" };
}

export function Index() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Comments dataPromise={loadData()} />
    </Suspense>
  );
}

Form actions

async function updateDataAndLoadNew() {
  // update data
  // load new data

  // return new data
  return { data: "some data" };
}

export default function Index() {
  const [state, action, isPending] = useActionState(
    async (prev, formData) => {
      return updateUserAndLoadNewData();
    },
    { data: "initial state, no data" } // can be null
  );

  if (state.error) {
    return `Error ${state.error.message}`;
  }

  return (
    <form action={action}>
      <input type="text" name="name" />
      <button type="submit" disabled={isPending}>
        Do it
      </button>
      {state.data && <p>Data {state.data}</p>}
    </form>
  );
}

For React version >=18

Starting with React 18 you can also use Suspense, but it's not yet recommended if you are not using frameworks that correctly implement it:

In React 18, you can start using Suspense for data fetching in opinionated frameworks like Relay, Next.js, Hydrogen, or Remix. Ad hoc data fetching with Suspense is technically possible, but still not recommended as a general strategy.

If not part of the framework, you can try some libs that implement it like swr.


Oversimplified example of how suspense works. You need to throw a promise for Suspense to catch it, show fallback component first and render Main component when promise it's resolved.

let fullfilled = false;
let promise;

const fetchData = () => {
  if (!fullfilled) {
    if (!promise) {
      promise = new Promise(async (resolve) => {
        const res = await fetch('api/data')
        const data = await res.json()

        fullfilled = true
        resolve(data)
      });
    }

    throw promise
  }
};

const Main = () => {
  fetchData();
  return <div>Loaded</div>;
};

const App = () => (
  <Suspense fallback={"Loading..."}>
    <Main />
  </Suspense>
);

For React version <=17

I suggest to look at Dan Abramov (one of the React core maintainers) answer here:

I think you're making it more complicated than it needs to be.

function Example() {
  const [data, dataSet] = useState<any>(null)

  useEffect(() => {
    async function fetchMyAPI() {
      let response = await fetch('api/data')
      response = await response.json()
      dataSet(response)
    }

    fetchMyAPI()
  }, [])

  return <div>{JSON.stringify(data)}</div>
}

Longer term we'll discourage this pattern because it encourages race conditions. Such as — anything could happen between your call starts and ends, and you could have gotten new props. Instead, we'll recommend Suspense for data fetching which will look more like

const response = MyAPIResource.read();

and no effects. But in the meantime you can move the async stuff to a separate function and call it.

You can read more about experimental suspense here.


If you want to use functions outside with eslint.

 function OutsideUsageExample({ userId }) {
  const [data, dataSet] = useState<any>(null)

  const fetchMyAPI = useCallback(async () => {
    let response = await fetch('api/data/' + userId)
    response = await response.json()
    dataSet(response)
  }, [userId]) // if userId changes, useEffect will run again

  useEffect(() => {
    fetchMyAPI()
  }, [fetchMyAPI])

  return (
    <div>
      <div>data: {JSON.stringify(data)}</div>
      <div>
        <button onClick={fetchMyAPI}>manual fetch</button>
      </div>
    </div>
  )
}
2 of 16
156

When you use an async function like

async () => {
    try {
        const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
        const json = await response.json();
        setPosts(json.data.children.map(it => it.data));
    } catch (e) {
        console.error(e);
    }
}

it returns a promise and useEffect doesn't expect the callback function to return Promise, rather it expects that nothing is returned or a function is returned.

As a workaround for the warning you can use a self invoking async function.

useEffect(() => {
    (async function() {
        try {
            const response = await fetch(
                `https://www.reddit.com/r/${subreddit}.json`
            );
            const json = await response.json();
            setPosts(json.data.children.map(it => it.data));
        } catch (e) {
            console.error(e);
        }
    })();
}, []);

or to make it more cleaner you could define a function and then call it

useEffect(() => {
    async function fetchData() {
        try {
            const response = await fetch(
                `https://www.reddit.com/r/${subreddit}.json`
            );
            const json = await response.json();
            setPosts(json.data.children.map(it => it.data));
        } catch (e) {
            console.error(e);
        }
    };
    fetchData();
}, []);

the second solution will make it easier to read and will help you write code to cancel previous requests if a new one is fired or save the latest request response in state

Working codesandbox

🌐
React
react.dev › reference › react › useEffect
useEffect – React
You can also rewrite using the async / await syntax, but you still need to provide a cleanup function: App.jsApp.js · ReloadClearFork · import { useState, useEffect } from 'react'; import { fetchBio } from './api.js'; export default function Page() { const [person, setPerson] = useState('Alice'); const [bio, setBio] = useState(null); useEffect(() => { async function startFetching() { setBio(null); const result = await fetchBio(person); if (!ignore) { setBio(result); } } let ignore = false; startFetching(); return () => { ignore = true; } }, [person]); return ( <> <select value={person} onChange={e => { setPerson(e.target.value); }}> <option value="Alice">Alice</option> <option value="Bob">Bob</option> <option value="Taylor">Taylor</option> </select> <hr /> <p><i>{bio ??
🌐
Devtrium
devtrium.com › posts › async-functions-useeffect
How to use async functions in useEffect (with examples) - Devtrium
August 14, 2021 - The issue here is that the first argument of useEffect is supposed to be a function that returns either nothing (undefined) or a function (to clean up side effects). But an async function returns a Promise, which can't be called as a function!
🌐
Marmelab
marmelab.com › blog › 2023 › 01 › 11 › use-async-effect-react.html
useAsyncEffect: The Missing React Hook
January 11, 2023 - But useEffect doesn’t accept asynchronous callbacks, as an async callback returns a Promise, and the return value of a side effect callback must be the cleanup function.
🌐
DEV Community
dev.to › stlnick › useeffect-and-async-4da8
`useEffect()` and `async` - DEV Community
August 10, 2020 - If you've learned the traditional class-based React Components and you're now trying to move into Hooks there's a few things along the way that'll throw you for a loop. One such thing that took a little digging for me is the combination of useEffect() - essentially the replacement for componentDidMount, componentDidUpdate, and componentWillUnmount - and async/await.
Find elsewhere
🌐
Allaroundjavascript
blog.allaroundjavascript.com › asynchronous-calls-in-react-useeffect-usestate
Asynchronous calls in React : useEffect() + useState()
January 12, 2025 - Note that you cannot make the whole callback inside useEffect async( otherwise it will create a loop) - that's why instead we declare an async function load inside and call it without awaiting.
🌐
Medium
medium.com › @caraerskine › my-first-time-using-an-async-await-function-in-a-useeffect-step-by-step-7bc8017ca7f2
My first time using an async/await function in a useEffect, step-by-step | by caraerskine | Medium
June 2, 2023 - My first time using an async/await function in a useEffect, step-by-step When the component mounts, fetch the user data! Got it. When a component renders, the UseEffect is executed after render. And …
🌐
Ultimate Courses
ultimatecourses.com › blog › using-async-await-inside-react-use-effect-hook
Using Async Await Inside React's useEffect() Hook - Ultimate Courses
May 9, 2022 - // ❌ Don't do this! useEffect(async () => { const users = await fetchUsers(); setUsers(users); return () => { // this never gets called, hello memory leaks... }; }, []); This WORKS, but you should avoid it. Why? Because React’s useEffect hook expects a cleanup function returned from it which is called when the component unmounts.
🌐
Medium
medium.com › @atshn.gunduz › fetch-async-useeffect-a5737846bf9c
fetch async useEffect. Fetching data asynchronously allows our… | by Ateshan Gunduz | Medium
December 8, 2023 - useEffect(() => { const data = async () => { const href = await fetchCat(); setCat(href); }; data(); }, []); In order to enhance performance, user experience, and responsiveness in React, fetching data asynchronously with useEffect is essential.
🌐
Docureacten
docureacten.github.io › managing asynchronous operations inside useeffect
Managing Asynchronous Operations Inside useEffect | React.js: Learn Easily with Examples
The useEffect hook allows you to perform side effects in function components. By default, it runs after the first render and after every update. Here’s a simple example: import React, { useEffect } from 'react'; function ExampleComponent() ...
🌐
DEV Community
dev.to › elijahtrillionz › cleaning-up-async-functions-in-reacts-useeffect-hook-unsubscribing-3dkk
Cleaning up Async Functions in React's useEffect Hook (Unsubscribing) - DEV Community
December 2, 2021 - Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
🌐
Medium
medium.com › @finnkumar6 › battle-of-the-asyncs-promises-async-await-and-useeffect-in-reactjs-b1c83128d03e
Battle of the Asyncs: Promises, Async/Await, and useEffect in ReactJS | by Aryan kumar | Medium
October 13, 2024 - Async/Await: The most readable and maintainable way to write async code, especially for larger applications. useEffect: The React-specific hook for handling side effects, with the need for a little extra care to manage async logic properly.
🌐
JavaScript in Plain English
javascript.plainenglish.io › using-async-functions-in-useeffect-best-practices-and-pitfalls-ec05d324e511
Using Async Functions in `useEffect`: Best Practices and Pitfalls | by Vishal Yadav | JavaScript in Plain English
October 14, 2024 - Do not directly use async functions in useEffect. Instead, define them inside the hook. Always handle cleanup for asynchronous operations to prevent memory leaks. Manage the dependency array carefully to control when the effect runs.
🌐
W3Schools
w3schools.com › react › react_useeffect.asp
React useEffect
React Compiler React Quiz React Exercises React Syllabus React Study Plan React Server React Interview Prep React Bootcamp React Certificate ... The useEffect Hook allows you to perform side effects in your components.
🌐
CodeMiner42
blog.codeminer42.com › home › posts › the miners' guide to code crafting › trainee frontend › async code demystified: promises, fetch, and useeffect
Async Code Demystified: Promises, Fetch, and useEffect
July 7, 2025 - To tie it all together, React’s useEffect hook empowers you to synchronize components with external systems, making side effects like fetching data or reacting to state changes more manageable.
🌐
LinkedIn
linkedin.com › pulse › how-use-asyncawait-react-useeffect-hook-hasibul-islam
How to Use Async / Await in the React useEffect() Hook
June 28, 2023 - To await an async function in the React useEffect() hook, wrap the async function in an immediately invoked function expression (IIFE).
🌐
React
react.dev › learn › synchronizing-with-effects
Synchronizing with Effects – React
In other words, useEffect “delays” a piece of code from running until that render is reflected on the screen.
🌐
Seanconnolly
seanconnolly.dev › async-useeffect
Async functions with React's useEffect hook · Sean Connolly
useEffect(() => { (async () => { try { await doSomething(); } catch (err) { console.error(err); } })(); }, []);