-
I have a highly reactive use case for mutations, where I want to make the optimistic update and also update cache with mutation response, exactly like in documentation. My problem is, that mutations happen too fast, and quite often the response from the first mutation overwrites optimistic updates from the second mutation. I'm looking for a way to conditionally update the cache from mutation response, only if no newer mutations exist with the same mutation key. I ended up for now with this solution: const isLastMutation = (submittedAt: number, queryKey: QueryKey) => {
const mutationCache = queryClient
.getMutationCache()
.getAll()
.filter((mutation) => mutation.options.mutationKey?.every((key, i) => key === queryKey[i]))
.map((mutation) => mutation.state);
const newerMutations = mutationCache.filter((mutation) => mutation.submittedAt > submittedAt);
return newerMutations.length === 0;
};
const mutation = useMutation({
// ...
onSuccess: (data) => {
if (!isLastMutation(mutation.submittedAt, ['my key'])) return;
queryClient.setQueryData<TaskDTO>([my key'], data);
}, But it depends on mutation.submittedAt, which I'm not sure how works with multiple mutations from the same hook call. And also I feel like I'm reinventing the wheel. So probably I don't see a better, more straightforward and declarative solution |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 1 reply
-
So with invalidation, it should be enough to check if any other mutation is currently running before you perform the update? So:
note that inside of With manual cache updates however, it’s more tricky because you can’t know if the data you’re getting is the latest. If you have concurrent mutations for the same key, then yes, you have to check
We also expose a |
Beta Was this translation helpful? Give feedback.
-
Regarding manual cache updates, if I understand the problem correctly, if we have mutation 1 and mutation 2, we can't know in which order they arrived (and executed) at the back end, and arrived at the FE, so we don't really know which mutation result has the latest state. And that is the root of the problem. (Do I understand it correctly?) The 1/2 true holistic solutions is to have sequential mutations, ensuring the order of mutations and that the last mutation is executed last and has the latest state in the result. (For me this solution won't make much sense, as mutations override each other state, and are executed in a fast tempo (5-7 mutations a second if a user is a power user). So mutations would create a growing queue. The 2/2 option is to invalidate data from the last finished mutation on the client side (the mutation is finished and no other mutations with the key are running at the moment), which means that all connected mutations are executed, and, data you'd get from the BE now is the latest. E.g. onSuccess: () => {
if (queryClient.isMutating({ mutationKey: ['my mutation key'] } === 1)
queryClient.invalidateQueries({ queryKey: ['my query key that represents mutated data'] })
}
} The downside of this option is an additional request for the data, that I already have, and that also elongates the feedback loop, as you wait for the additional call to be finished, before you know as a user that data was digested by the server properly. The solution with manual cache update is and would remain risky, as you always have a chance of overriding data with the response from the mutation, that wasn't executed last at the BE and doesn't have the true state of the BE. The only anchors I have are: when and which mutation was called from the FE, e.g. submittedAt or mutationId. |
Beta Was this translation helpful? Give feedback.
-
I also realise now, that regardless of what I want to do with that information (invalidation or cache direct update), there's no reason to filter the mutation cache by |
Beta Was this translation helpful? Give feedback.
data that you might or might not have. An invalidation at the end is the best way to ensure eventual consistency.