Yassine
Yassine

Reputation: 1231

How to call an async function inside useEffect() in React?

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

Upvotes: 113

Views: 124054

Answers (7)

YingYang
YingYang

Reputation: 984

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(() => getData(1).then(setData), []);

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

Upvotes: 10

Owais ul wara
Owais ul wara

Reputation: 51

You can also create a self-calling async function.

    useEffect(() => {
    (async () => {
        try {
            const data = await getData(1);
            setData(data);
        } catch (err) {
            console.log(err);
        }
    })();
}, []);

Upvotes: 0

zilijonas
zilijonas

Reputation: 3785

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>
        );
    }

Upvotes: 10

brunettdan
brunettdan

Reputation: 1057

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.

Upvotes: 3

JamesK
JamesK

Reputation: 49

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();
}, []);

Upvotes: 0

hsusanoo
hsusanoo

Reputation: 924

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);
  })();

}, []);

Upvotes: 56

Fraction
Fraction

Reputation: 12993

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();
}, []);

Upvotes: 182

Related Questions