90

I would like to call an async function and get the result for my UseEffect.

The fetch api examples I found on the internet are directly made in the useEffect function. If my URL changes, I must patch all my fetchs.

When I tried, I got an error message.

This is my code.


    async function getData(userId) {
        const data = await axios.get(`http://url/api/data/${userId}`)
            .then(promise => {
                return promise.data;
            })
            .catch(e => {
                console.error(e);
            })
            return data;
    }
    

    function blabla() {
        const [data, setData] = useState(null);
    
        useEffect(async () => {
            setData(getData(1))
        }, []);
    
        return (
            <div>
                this is the {data["name"]}
            </div>
        );
    }

index.js:1375 Warning: An effect function 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
mikemaccana
  • 110,530
  • 99
  • 389
  • 494
Yassine Layachi
  • 1,001
  • 1
  • 6
  • 8
  • 2
    If a function returns a promise, you can `await` or `.then(...)`, not both. – Omagerio Jul 01 '19 at 15:36
  • This answers the question I guess, but in no way does this actually solves the problem. You are effectively starting a promise that might finish anytime. If your async job is not related to components's life cycle, then fine. But otherwise, you're going to run head first into troubles and hard to debug rendering bugs. I'm not experienced enough with React to be sure, but I feel like this messes with react's dependency system too. – Romain Vincent Apr 20 '21 at 08:44
  • See also [React Hook Warnings for async function in useEffect](https://stackoverflow.com/q/53332321/1048572) – Bergi Nov 11 '21 at 19:32

6 Answers6

151

Create an async function inside your effect that wait the getData(1) result then call setData():

useEffect(() => {
  const fetchData = async () => {
     const data = await getData(1);
     setData(data);
  }

  fetchData();
}, []);
Fraction
  • 11,668
  • 5
  • 28
  • 48
  • 8
    Why is this any different than if the async function were defined outside of the useEffect hook? – Leland Reardon Aug 17 '21 at 15:43
  • 9
    @LelandReardon If you want to define the async function outside of the `useEffect` hook, you have to add it to the dependency list of `useEffect` and wrap its definition into a `useCallback` with the necessary dependencies to prevent unnecessary calls, for more info check the react documentation [here](https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies) – Fraction Aug 24 '21 at 11:34
  • Does this not result in an unhandled promise rejection, if `getData` rejects? – Wyck Aug 05 '23 at 20:19
43

If you're invoking it right-away you might want to use it as an anonymous function:

useEffect(() => {

  (async () => {
     const data = await getData(1);
     setData(data);
  })();

}, []);
hsusanoo
  • 774
  • 7
  • 18
  • What are the benefits? – Remi Oct 06 '20 at 11:58
  • 1
    @Remi The function will be invoked right away and won't be used anywhere else, so it doesn't need a name or any predefinition – hsusanoo Oct 06 '20 at 12:10
  • 3
    How about cancelling the request if the component get's unmounted? – Remi Oct 06 '20 at 12:15
  • @Remi this is unrelated to OP's question, you can can ask this question on a separate thread as it might have different implementations, most of which are unrelated to whether you use an anonymous async or a predefined one for fetching data – hsusanoo Oct 06 '20 at 12:23
  • 1
    Edge case. If you're not cancelling it then React will set a state even when this component is unmounted. Think some test libraries will even complain. Therefore this example isn't recommended persé. – Remi Oct 06 '20 at 14:45
  • ``` useEffect(() => { let data; (async () => { data = await getData(1); })(); console.log(data); ///->>> gives undefined }, []) ``` Any reason ?? or way to get value from async function without using a new state variable ? – Yusuf Jan 05 '21 at 16:02
  • At least in React Native 0.64.3, this will lead to a memory leak with the async call being done until the app freezes. – Juha Untinen Aug 19 '22 at 05:16
  • not recommended TBH – Vahid Amiri Mar 06 '23 at 22:25
9

It would be best if you did what the warning suggests - call the async function inside the effect.

    function blabla() {
        const [data, setData] = useState(null);

        useEffect(() => {
            axios.get(`http://url/api/data/1`)
             .then(result => {
                setData(result.data);
             })
             .catch(console.error)
        }, []);

        return (
            <div>
                this is the {data["name"]}
            </div>
        );
    }

If you want to keep the api function outside of the component, you can also do this:

    async function getData(userId) {
        const data = await axios.get(`http://url/api/data/${userId}`)
            .then(promise => {
                return promise.data;
            })
            .catch(e => {
                console.error(e);
            })
            return data;
    }


    function blabla() {
        const [data, setData] = useState(null);

        useEffect(() => {
            (async () => {
                const newData = await getData(1);
                setData(newData);
            })();
        }, []);

        return (
            <div>
                this is the {data["name"]}
            </div>
        );
    }
zilijonas
  • 3,285
  • 3
  • 26
  • 49
  • 2
    Thanks for the answer. Unfortunately, this is exactly what i don't want to do. If my URL changes, i must patch it on all the files i made a fetch. For scalability, i'm trying to do something more like the first code i posted. – Yassine Layachi Jul 01 '19 at 16:00
  • You could use environment variables – Monstar Mar 22 '22 at 21:28
8

Since getData returns a Promise, you could just use .then. In your case, this is much simpler than writing an async function and directly calling it.

Additionally, since axios.get already returns a Promise, your getData function doesn't need to be marked async. This is a simplified, working version of your code:

function getData(userId) {
    return axios.get(`http://url/api/data/${userId}`)
        .then(promise => promise.data)
        .catch(e => {
            console.error(e);
        });
}


function blabla() {
    const [data, setData] = useState(null);

    useEffect(async () => {
        getData(1).then(setData);
    }, []);

    return (
        <div>
            this is the {data["name"]}
        </div>
    );
}
YingYang
  • 888
  • 2
  • 15
  • 31
2

Component might unmount or re-render with different someId before await is resolved:

const unmountedRef = useRef(false);
useEffect(()=>()=>(unmountedRef.current = true), []);

useEffect(() => {
  const effectStale = false; // Don't forget ; on the line before self-invoking functions
  (async function() {
    // You can await here
    const response = await MyAPI.getData(someId);

    /* Component has been unmounted. Stop to avoid
       "Warning: Can't perform a React state update on an unmounted component." */
    if(unmountedRef.current) return;

    /* Component has re-rendered with different someId value
       Stop to avoid updating state with stale response */
    if(effectStale) return;

    // ... update component state
  })();
  return ()=>(effectStale = true);
}, [someId]);

Consider using Suspense for data that needs to be loaded before component is mounted.

brunettdan
  • 997
  • 9
  • 7
0

You can still define the async function outside of the hook and call it within the hook.

const fetchData = async () => {
   const data = await getData(1);
   setData(data);
}

useEffect(() => {
  fetchData();
}, []);
JamesK
  • 49
  • 9
  • This isn't recommended because it's not possible to cancel the `setData` inside this async function. – itwasmattgregg Nov 10 '20 at 03:25
  • 2
    what do you mean by `cancel` ? – Yusuf Jan 05 '21 at 16:03
  • @ itwasmattgregg can you give an example of canceling please or elaborate further? I haven't seen any definitive answer for why you can't do this, other than just best practice. However, best practice could also be factoring these function definitions to another file altogether. – Devin B. Mar 03 '21 at 02:08
  • 5
    @Dev if component gets unmounted while getData is in-flight then setData tries to mutate state after the fact, react will throw a warning that it "indicates a memory leak", it may or may not be but component shouldn't do stuff when it's no longer around. this is avoided by returning a function from useEffect (react calls it on unmount) that sets a flag then that flag can be checked before calling setData. – stt Mar 04 '21 at 20:13