1

In my simple ToDo app i am using useQuery() to fetch ToDo's from the server as well as useMutation() to create, update and delete ToDo's. When a mutation is successful, i invalidate the query so it gets refetched.

If i press the delete button of a ToDo item, i want the corresponding button to show a loading spinner until the mutation is done and the new ToDo's have been fetched. For that purpose i am using the useIsFetching() hook in my components, which works fine. However, here is the problem:

If i now execute a mutation, every button (meaning the "Delete" button as well as the "Submit" and "Save changes" button to post or update a ToDo) will show the loading spinner instead of just the one that i pressed. This makes sense since they all depend on the same value of useIsFetching(). I need a way to figure out which mutation led to the refetching of the query so i can conditionally render the loading spinner for the appropriate button. This seems to be a very common problem for me yet i cannot find a (not overcomplicated) solution for it. Is there something i'm missing?

2 Answers 2

1

The solution Ahmed Sbai said above is good (you can use state instead of local variables), and here is another approach for you.

You can check condition based on isLoading in the object returned from useMutation().

Updated: As written in this TkDodo's blog, the "magic" is here:

If you want your mutation to stay in loading state while your related queries update, you have to return the result of invalidateQueries() from the callback.

Therefore, you won't need to use the useIsFetching() hook, too.

function App() {
  const addMutation = useMutation({
    mutationFn: (newTodo) => {
      return axios.post('/todos', newTodo)
    },
    onSuccess: () => {
      return queryClient.invalidateQueries({
        queryKey: ['todos'],
      })
    }
  })

  const updateMutation = useMutation({
    mutationFn: (id, data) => {
      return axios.patch(`/todos/${id}`, data)
    },
    onSuccess: () => {
      return queryClient.invalidateQueries({
        queryKey: ['todos'],
      })
    }
  })

  const deleteMutation = useMutation({
    mutationFn: (id) => {
      return axios.delete(`/todos/${id}`)
    },
    onSuccess: () => {
      return queryClient.invalidateQueries({
        queryKey: ['todos'],
      })
    }
  })

  return (
    <div>
      {/* ... */}
      <button
        onClick={() => addMutation.mutate(...)}
        loading={addMutation.isLoading}
      >
        Submit
      </button>

      <button
        onClick={() => updateMutation.mutate(...)}
        loading={updateMutation.isLoading}
      >
        Save changes
      </button>

      <button
        onClick={() => deleteMutation.mutate(...)}
        loading={deleteMutation.isLoading}
      >
        Delete
      </button>
    </div>
  )
}

If you want any further information, please read more in Docs.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for the answer! But this is not working sadly as i want to show the loading spinner not only until mutation is finished but also until refetching of the query is finished. That is why i am using the useIsFetching() hook. But this useIsFetching hook gives me no information about what is causing the refetching.
I'm sorry for not covering the case loading until the refetching is finished. Finally, I found a completely solution from TkDodo's blog (tkdodo.eu/blog/mastering-mutations-in-react-query). Thanks to this "awaited Promises", you won't need to mess things up with redux anymore, I guess. Please refer to my updated answer above.
I tested with my deleteMutation and it worked as expected! ^^ Hope this can help you!
1

You can simply create a variable var loadingType = 0 and update its value each time the user click on a button for example if the delete button is clicked then loadingType = 1 if update button loadingType = 2, etc. Then based on the value of loadingType you know which loading spinner you have to use.

2 Comments

Thanks for the answer! The problem is that i have these buttons in different components, which means i would have to use react context api or redux to give all of them access to this variable. I hoped that there would be a simple react query solution for this as it is (in my view) a common problem. But guess i have to do it that way.
Yes you can share the value with redux

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.