T
TanStack•10mo ago
genetic-orange

setQueryData inside mutation behavior

I copied following example from the course:
function useUpdateUser() {
const queryClient = useQueryClient()

return useMutation({
mutationFn: updateUser,
onSuccess: (newUser) => {
queryClient.setQueryData(['user', newUser.id], newUser)
}
})
}
function useUpdateUser() {
const queryClient = useQueryClient()

return useMutation({
mutationFn: updateUser,
onSuccess: (newUser) => {
queryClient.setQueryData(['user', newUser.id], newUser)
}
})
}
What happens if the isPending of this mutation is being used to show or hide specific components on a page. For example a component showing the user details in a form, using as initial values those provided by useQuery matching the key of the mutation above. queryClient.setQueryData is being called in the onSuccess which I think means that while calling queryClient.setQueryData, isPending inside the component using the mutation is actually false, is that correct? I think I saw an issue locally where my form (Mantine use-form) was using outdated initial values due to queryClient.setQueryData still being 'in progress'. Is this something someone else also experienced, if so, how should I work around this? I assume the data from useQuery is updated in a later render cycle, pushing the new props to the form component but they are not taking into account as the form already rendered.
7 Replies
afraid-scarlet
afraid-scarlet•10mo ago
if you call queryClient.setQueryData in onSuccess, it's possible that there is one render cycle where mutation is still pending, but the query already has the new data. I don't know why that would be a problem though?
genetic-orange
genetic-orangeOP•10mo ago
Ah so you are suggesting it's even the other way around. Could there ever be a case where isPending from the mutation is false but the data from useQuery is still outdated for a render cycle?
afraid-scarlet
afraid-scarlet•10mo ago
I wouldn't think so. setQueryData is synchronous, and the mutation awaits onSuccess because that one can be async. But then there's batching and scheduling by both react-query and react itself, so "it depends"
genetic-orange
genetic-orangeOP•10mo ago
Do you mean that if I would await for 5 seconds in the onSuccess, the isPending of that mutation will also stay true for those 5 full seconds? By the way I really appreciate you taking your time to answer all of our question. I do realise it's all out of goodwill. You are awesome! Update: is just tried and indeed the isPending is true until onSuccess completed, TIL 🙂 @TkDodo 🔮 I just tried adding the onSuccess also in the component:
onSuccess: async (newUser) => {
console.log('>>> isPending', isPending);
await delay(3000); // await 3 seconds
},
onSuccess: async (newUser) => {
console.log('>>> isPending', isPending);
await delay(3000); // await 3 seconds
},
isUpdating is always false and the 3 seconds are not awaited so it's behaving differently than the onSuccess in the hook. Is this expected?
afraid-scarlet
afraid-scarlet•10mo ago
Yes
afraid-scarlet
afraid-scarlet•10mo ago
Mastering Mutations in React Query
Learn all about the concept of performing side effects on the server with React Query.
genetic-orange
genetic-orangeOP•10mo ago
@TkDodo 🔮 Makes sense, moving all logic related stuff inside the hook callbacks. Currently I have some use-cases where I 'nest' mutation logic on component level:
const generateReport = () => {
generateReportMutate(reportId, {
onSuccess: (report) => {
persistReportMutate(report, {
onSuccess: (updatedSession) => {
...
queryClient.setQueryData(...)
...
},
});
},
});
};
const generateReport = () => {
generateReportMutate(reportId, {
onSuccess: (report) => {
persistReportMutate(report, {
onSuccess: (updatedSession) => {
...
queryClient.setQueryData(...)
...
},
});
},
});
};
Not sure if this is even a good practice. How would you suggest handling such case? Move all of this over to a single hook resulting in a single isPending? https://tkdodo.eu/blog/mastering-mutations-in-react-query#mutate-or-mutateasync "The only situations where I've found mutateAsync to be superior is when you really need the Promise for the sake of having a Promise. This can be necessary if you want to fire off multiple mutations concurrently and want to wait for them all to be finished, or if you have dependent mutations where you'd get into callback hell with the callbacks.", seems like that really applies for my case.

Did you find this page helpful?