T
TanStack•10mo ago
fascinating-indigo

Atomic query invalidation / transaction

Hello everyone ! I am in the process of migrating my Remix app (loader / actions part) to react query and loving it so far. I think the only missing piece is what I call "atomic updates" (or transactions ?). Currently, let's say I write this code :
useMutation({
mutationFn: ...,
onSuccess: () => {
// Serial or Promise.all does not matter
await invalidateQueries({ queryKey: ['posts'] })
await invalidateQueries({ queryKey: ['campaigns'] })
}
})
useMutation({
mutationFn: ...,
onSuccess: () => {
// Serial or Promise.all does not matter
await invalidateQueries({ queryKey: ['posts'] })
await invalidateQueries({ queryKey: ['campaigns'] })
}
})
The UI would be updated several times (mutation, then posts refreshed / campaigns refreshed). In the previous version of the app, Remix would wait for all loaders to be refetched to render the current route, leading to a more "stable" experience (ie all changes are committed at once). Is there some concept that I missed in the docs ? Or is it something that is currently not supported by react query ? Basically, I would imagine something like this :
useMutation({
mutationFn: ...,
onSuccess: () => {
await queryClient.transaction(() => {
await invalidateQueries({ queryKey: ['posts'] })
await invalidateQueries({ queryKey: ['campaigns'] })
})
// Now all subscribers are notified of the changes above
}
})
useMutation({
mutationFn: ...,
onSuccess: () => {
await queryClient.transaction(() => {
await invalidateQueries({ queryKey: ['posts'] })
await invalidateQueries({ queryKey: ['campaigns'] })
})
// Now all subscribers are notified of the changes above
}
})
Hope my explanation is clear (English is not my mothertongue!) 😄 Thanks you all, Antoine
6 Replies
like-gold
like-gold•10mo ago
This should do the trick:
useMutation({
mutationFn: ...,
onSuccess: () => {
await queryClient.invalidateQueries({
predicate: (query) => query.queryKey.includes("posts") || query.queryKey.includes("campaigns")
});
}
})
useMutation({
mutationFn: ...,
onSuccess: () => {
await queryClient.invalidateQueries({
predicate: (query) => query.queryKey.includes("posts") || query.queryKey.includes("campaigns")
});
}
})
Check https://tanstack.com/query/latest/docs/framework/react/guides/filters#query-filters for more details. It would also perhaps work if you give a common prefex in the query key to invalidate both posts and campaigns e.g. ['siteName', 'blog', 'posts'], ['siteName', 'blog', 'campaigns '], both can be invalidated with queryClient.invalidateQueries({ queryKey: ['siteName', 'blog'])
TanStack | High Quality Open-Source Software for Web Developers
Headless, type-safe, powerful utilities for complex workflows like Data Management, Data Visualization, Charts, Tables, and UI Components.
From An unknown user
From An unknown user
like-gold
like-gold•10mo ago
React Query meets React Router
React Query and React Router are a match made in heaven.
Automatic Query Invalidation after Mutations
Even though there is nothing built into React Query, it doesn't need a lot of code to implement automatic query invalidation in user-land thanks to the global cache callbacks.
Effective React Query Keys
Learn how to structure React Query Keys effectively as your App grows
quickest-silver
quickest-silver•10mo ago
imo this gets fixed by useSuspenseQuery, when you invalidate both queries, they will both start fetching new data and would both suspend, then if you have a <Suspense /> anywhere above the components with the queries, then you can render a fallback loader until all useSuspenseQuerys fetched their data this pairs nicely with tanstack router's pending component, while queries are fetching they suspend to the router's pending component You can also see how to keep the old data while fetching new data in the article from this discussion: https://discord.com/channels/719702312431386674/1292750498969026652/1296499163164315692
fascinating-indigo
fascinating-indigoOP•10mo ago
Thanks @DogPawHat , I'm gonna try the predicate function but I'm not sure it is synchronizing the subscribers, only the refetch trigger, no ? Those articles are really good, I already read them but there are some subtilities that they do not cover (eg invalidating in action can lead to UI updates before the end of the action)
xenial-black
xenial-black•10mo ago
usually, queries should form a hierarchy, so it should be possible to target them all with the same key. predicate also works, and lastly, you can batch manually with:
await notifyManager.batch(() => Promise.all(...))
await notifyManager.batch(() => Promise.all(...))
notifyManager can be imported from react-query
fascinating-indigo
fascinating-indigoOP•10mo ago
I completely missed that feature in the docs ! Thanks a lot, I'll have a look 🙂

Did you find this page helpful?