Home > Software design >  React setState inside useEffect async
React setState inside useEffect async

Time:10-08

I am attempting to perform a series of Axios requests inside the useEffect() of a react component. I am aware that these requests are asynchronous, and I should maintain a piece of "loading" state that specifies if series of requests have been completed.

    const [state, updateState] = useState([])
    const [loading, setLoading] = useState(true)

    useEffect(() => {
        let innerstate = []

        allRespData.map(single_response => {
            axios.post("<URL>", {
                raw_narrative: single_response[index].response
            })
            .then((response) => {
                innerstate.push(response.data)
            }); 
        })  
        updateState(innerstate)
        setLoading(false)
    }, []);

    if (loading)
        return (<h3>  Loading </h3>)
    else {
        console.log(state)
        return (<h3> Done </h3>)
    }

I would expect the output from the above code to be a list containing the data of each response. Unfortunately, I think that data only arrives midway through the console.log() statement, as initially an empty list [] is logged, however the list is expandable- therein my expected content is visible.

I am having a hard time doing anything with my state at the top, because the list length is constantly 0, even if the response has already loaded (loading == false).

enter image description here

How can I assert that state has been updated? I assume the problem is that the loading variable only ensures that a call to the updateState() has been made, and does not ensure that the state has actually been updated immediately thereafter. How can I ensure that my state contains a list of response data so that I can continue doing operations on the response data, for example, state.forEach().

CodePudding user response:

You're not awaiting any of the requests, so updateState will get called before any of the responses have had time to come back. You'll be setting the state as [] every time. You also need to return your axios.post or the data won't get passed to .then

There are lot of nicer ways to handle this (I'd recommend looking at the react-query library, for example). However, to make this work as it is, you could just use Promise.all(). Something like:

useEffect(() => {
  Promise.all(
    allRespData.map(single_response =>
      axios
        .post('<URL>', { raw_narrative: single_response[index].response })
        .then(response => response.data)
        .catch(error => {
          // A single error occurred
          console.error(error);
          // you can throw the error here if you want Promise.all to fail (or just remove this catch)
        })
    )
  )
    // `then` will only be called when all promises are resolved
    .then(responses => updateState(responses))
    // add a `.catch` if you want to handle errors
    .finally(() => setLoading(false));
}, []);
  • Related