116

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
4
  • 2
    If a function returns a promise, you can await or .then(...), not both. Commented Jul 1, 2019 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. Commented Apr 20, 2021 at 8:44
  • See also React Hook Warnings for async function in useEffect Commented Nov 11, 2021 at 19:32
  • @Omagerio you can do both just fine. .then returns a promise, which can be awaited like any other. You don't have to, but it can arguably lead to cleaner code, like if you want to map the raw result of an API call into a more convenient object before returning it. Working example (see code in home.component.ts): codesandbox.io/p/devbox/boilerplate-forked-3dm6v6 Commented Apr 17 at 1:38

7 Answers 7

188

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();
}, []);
Sign up to request clarification or add additional context in comments.

5 Comments

Why is this any different than if the async function were defined outside of the useEffect hook?
@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
Does this not result in an unhandled promise rejection, if getData rejects?
It does but then the error handling is dependent on case-by-case basis. And without additional context doing try...catch(error) { console.error(error) } might be worse than propagating to the nearest error boundary.
To clarify @Fraction's comment if the function is defined outside of the React Component (or itself has no hook dependencies), then it never needs to be declared as a dependency. If a function has dependencies and you declare those as dependencies in useEffect(), and useEffect() is the only place that function is used, there is no point in wrapping it in useCallback().
58

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

}, []);

8 Comments

What are the benefits?
@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
How about cancelling the request if the component get's unmounted?
@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
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é.
|
10

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

2 Comments

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.
You could use environment variables
10

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

Comments

3

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.

Comments

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

4 Comments

This isn't recommended because it's not possible to cancel the setData inside this async function.
what do you mean by cancel ?
@ 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.
@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.
0

You can also create a self-calling async function.

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.