Search code examples
async-awaitreact-querytanstackreact-query

Multiple React Query mutateAsync issue


The objective is simple:

I want to run both "mutateAsyn" one after other and only when both where "success" redirect to other page...

This is the code I am using:

const changeStatus = useChangeConsultStatus();
    const editConsult = useEditConsulta();

    async function onClickAttended() {
        
        await editConsult.mutateAsync({ consultID: consultID, consult: consultValues });
        await changeStatus.mutateAsync({ consultID: consultID, status: "PENDING" });

        console.log("Edit consult status:", editConsult.status ) //Edit consult status: iddle
        console.log("Change status consult status:", changeStatus.status ) // Change status consult status: success

        If (editConsult.status === "success" && changeStatus.status === "success"){
        router.push("/application");
}
    }

When I run the function "onClickAttended" the both "mutateAsync" runs in "Parallel" not one after the other was finished... so when the IF runs the condition not works... because the "editConsult mutateAsync" is not finished yet...

¿How can I make both runs one after other? to achieve the If condition


Solution

  • The problem is in the expectation that state you close over from your component / custom hook render function (editConsult) will magically change after you've called a function (async or not) inside an event handler. That is never the case because it fundamentally isn't how React works. React will always see the value from the time it created in the closure, which is what makes React predictable.

    Consider this example, which also won't work:

    const [count, setCount] = useState(0)
    
    function onClickAttended() {
      setCount(23)
      console.log(count)
    }
    

    The log statement will not log 23 - it will log 0. It also doesn't change if we make the function async or a "long" operation inside it that will re-render the component. This function instance will have count:0 in it's closure and that's the only thing it will ever know.

    React Query is effectively also just built on React primitives. So your custom hooks will have a status, but it cannot change after you call mutateAsync. Luckily for you, you don't need to: mutateAsync returns a Promise that fulfills with data. If it doesn't error, it will be success:

    const changeStatus = useChangeConsultStatus();
    const editConsult = useEditConsulta();
    
    async function onClickAttended() {
      try {
        await editConsult.mutateAsync({ consultID: consultID, consult: consultValues });
        await changeStatus.mutateAsync({ consultID: consultID, status: "PENDING" });
        // if you reach this point, both mutations were successful
        router.push("/application");
      } catch (e) { ... }
    }
    

    When I run the function "onClickAttended" the both "mutateAsync" runs in "Parallel" not one after the other was finished

    This is largely impossible, because await ... followed by another await ... will execute both mutations in serial - that's how async/await works.

    The most likely explanation is that your mutationFn for the first mutation doesn't await or resolve the promise from the function that makes the request. You haven't posted the code but if it looks like this:

    const useEditConsulta = () => useMutation({
      mutationFn: (params) => {
        performEditConsultaRequest(params)
      }
    })
    

    then it will fire the request, but the mutation will not wait until it's finished - it will resolve immediately. You always need to return or await the promise from those functions:

    const useEditConsulta = () => useMutation({
      mutationFn: (params) => {
        return performEditConsultaRequest(params)
      }
    })