T
TanStack•7mo ago
genetic-orange

Automatic Query Invalidation

Hi guys, I have read Automatic Query Invalidation after Mutations, and I pretty like meta option approach, but in some case I need to access to the payload I pass to the mutation to invalidate a specific queryKey. How can I combine it with the meta option approach?
18 Replies
other-emerald
other-emerald•7mo ago
What you return in the mutationFn should be available... I think that's what Dominik said in a semi-recent response. I'd have to go dig for it
genetic-orange
genetic-orangeOP•7mo ago
useMutation({
mutationFn: (userId) => api.call(userId),
meta: {
invalidates: [['users'], userId],
},
})
useMutation({
mutationFn: (userId) => api.call(userId),
meta: {
invalidates: [['users'], userId],
},
})
I mean, how can I pass the userId from mutationFn to meta object?
other-emerald
other-emerald•7mo ago
So you're using mutate(userId)? I think if you return userId from your mutationFn, say you do...
mutationFn: async (userId) => {
const result = await api.call(userId)

return {
...result,
userId
}
}
mutationFn: async (userId) => {
const result = await api.call(userId)

return {
...result,
userId
}
}
Then you'd have what you need?
genetic-orange
genetic-orangeOP•7mo ago
Sorry, I don't get it. If I return the object in side mutationFn, then how can I access it in meta option?
other-emerald
other-emerald•7mo ago
You have both mutation and meta available. You might also consider doing a hook for each user, so having something like useUser(userId) and meta will have it
other-emerald
other-emerald•7mo ago
You also have access to variables in the global cache callbacks, which is the input to mutate
other-emerald
other-emerald•7mo ago
That's the piece I was trying to get to, thanks
genetic-orange
genetic-orangeOP•7mo ago
Yeah I know about variables in the global cache callbacks, but how can I access the variables in the global cache callbacks in a useMutation? I mean, I want to pass the queryKey to the global success callback, and it will be invalidated here As I mentioned above, in some cases, I need to access the payload from mutation func. Passing payload into the hook is not my case
other-emerald
other-emerald•7mo ago
It sounds like you want a combination of meta and variables within your invalidations. Not sure you can confidently do that across an entire application which is why you might want a hook per user mutation
genetic-orange
genetic-orangeOP•7mo ago
Yes, that's what I mean
other-emerald
other-emerald•7mo ago
I've come to learn trying to use less hooks is the wrong approach. Moving it to a per user sounds better
other-emerald
other-emerald•7mo ago
onError of the global cache callbacks has this signature:
onError?: (error: unknown, variables: unknown, context: unknown, mutation: Mutation) => Promise<unknown>
onError?: (error: unknown, variables: unknown, context: unknown, mutation: Mutation) => Promise<unknown>
so it gets variables, and with mutation.meta, you get meta. You can invalidate depending on those
genetic-orange
genetic-orangeOP•7mo ago
Btw, may I ask a question about your blog post? Can I make await queryKey optional? For example, invalidates no need to await, and awaits needs to be awaited. Sorry, if my question is silly 😅
useMutation({
mutationFn: updateLabel,
meta: {
invalidates: ['all'],
awaits: ['labels'],
},
})
useMutation({
mutationFn: updateLabel,
meta: {
invalidates: ['all'],
awaits: ['labels'],
},
})
No description
other-emerald
other-emerald•7mo ago
sure? this is just an example implementation that I've found useful. But it's all custom
genetic-orange
genetic-orangeOP•7mo ago
I mean, how can I implement it? Hi, sorry if I bother you. I just want to ask if this is the correct way to optional invalidate queryKeys?
declare module "@tanstack/react-query" {
interface Register {
mutationMeta: {
invalidates?: Array<QueryKey> | QueryKey;
awaits?: Array<QueryKey> | QueryKey;
};
}
}

const isArrayOfQueryKeys = (
invalidates: QueryKey | QueryKey[],
): invalidates is QueryKey[] =>
invalidates.every((keys) => Array.isArray(keys));

const queryClient = new QueryClient({
mutationCache: new MutationCache({
onSuccess: async (_data, _variables, _context, mutation) => {
if (mutation.meta?.invalidates) {
const invalidateKeys = isArrayOfQueryKeys(mutation.meta.invalidates)
? mutation.meta.invalidates
: [mutation.meta.invalidates];

queryClient.invalidateQueries({
predicate: (query) =>
invalidateKeys.some((queryKey) =>
matchQuery({ queryKey }, query),
) ?? false,
});
}

if (mutation.meta?.awaits) {
const awaitKeys = isArrayOfQueryKeys(mutation.meta.awaits)
? mutation.meta.awaits
: [mutation.meta.awaits];
await queryClient.invalidateQueries({
predicate: (query) =>
awaitKeys.some((queryKey) => matchQuery({ queryKey }, query)) ??
false,
});
}
},
}),
})
declare module "@tanstack/react-query" {
interface Register {
mutationMeta: {
invalidates?: Array<QueryKey> | QueryKey;
awaits?: Array<QueryKey> | QueryKey;
};
}
}

const isArrayOfQueryKeys = (
invalidates: QueryKey | QueryKey[],
): invalidates is QueryKey[] =>
invalidates.every((keys) => Array.isArray(keys));

const queryClient = new QueryClient({
mutationCache: new MutationCache({
onSuccess: async (_data, _variables, _context, mutation) => {
if (mutation.meta?.invalidates) {
const invalidateKeys = isArrayOfQueryKeys(mutation.meta.invalidates)
? mutation.meta.invalidates
: [mutation.meta.invalidates];

queryClient.invalidateQueries({
predicate: (query) =>
invalidateKeys.some((queryKey) =>
matchQuery({ queryKey }, query),
) ?? false,
});
}

if (mutation.meta?.awaits) {
const awaitKeys = isArrayOfQueryKeys(mutation.meta.awaits)
? mutation.meta.awaits
: [mutation.meta.awaits];
await queryClient.invalidateQueries({
predicate: (query) =>
awaitKeys.some((queryKey) => matchQuery({ queryKey }, query)) ??
false,
});
}
},
}),
})
other-emerald
other-emerald•7mo ago
I don't think this works because you're making a new QueryClient, not using the stable one in your app
genetic-orange
genetic-orangeOP•7mo ago
No, it's a stable queryClient
other-emerald
other-emerald•7mo ago
Stable doesn’t mean it’s the same one that has all your cached data. You’re instantiating a new one here and only using it locally Oh never mind your spacing was odd. I see it's an isArrayOfQueryKeys function and then a queryClient constant

Did you find this page helpful?