T
TanStack16mo ago
adverse-sapphire

Help needed with Tanstack Query for Vue

I'm trying to make a demo of a simple app with optimistic update but I can't make the mutation showing as pending in another component from where the mutation was triggered. For example I have users list and add users page when I trigger the mutation inside the Add User component and try to listen to the same mutation inside UsersList the mutation shows as idle even though it's mutating in the background. I wrapped the pending state inside a computed property, I'm using Vue3 Composition API with Tanstack Query version: 5.27.5
10 Replies
adverse-sapphire
adverse-sapphireOP16mo ago
const { mutateAsync, isPending } = addUserMutation();

const onSubmit = handleSubmit(async (values) => {
try {
queryClient.setQueryData(["users"], (old: User[]) => {
const users = [
...old,
{
...values,
id: "1",
firstName: "",
lastName: "",
lastSignInAt: null,
emailAddresses: [{ emailAddress: values.email }],
},
];

return users;
});

mutateAsync(values, {
onSuccess: () => {
console.log("success");
},
onSettled: () => {
console.log("settled");
},
});

router.push("/users");
} catch (error) {
console.log(error);
}
});
const { mutateAsync, isPending } = addUserMutation();

const onSubmit = handleSubmit(async (values) => {
try {
queryClient.setQueryData(["users"], (old: User[]) => {
const users = [
...old,
{
...values,
id: "1",
firstName: "",
lastName: "",
lastSignInAt: null,
emailAddresses: [{ emailAddress: values.email }],
},
];

return users;
});

mutateAsync(values, {
onSuccess: () => {
console.log("success");
},
onSettled: () => {
console.log("settled");
},
});

router.push("/users");
} catch (error) {
console.log(error);
}
});
const mutation = addUserMutation();

const isPending = computed(() => {
return mutation.isPending;
});
const mutation = addUserMutation();

const isPending = computed(() => {
return mutation.isPending;
});
other-emerald
other-emerald16mo ago
you need to use useMutationState composable to get the state of mutation in MutationCache
other-emerald
other-emerald16mo ago
useMutationState | TanStack Query Vue Docs
useMutationState is a hook that gives you access to all mutations in the MutationCache. You can pass filters to it to narrow down your mutations, and select to transform the mutation state. Example 1: Get all variables of all running mutations
other-emerald
other-emerald16mo ago
Optimistic Updates | TanStack Query Vue Docs
Vue Query provides two ways to optimistically update your UI before a mutation has completed. You can either use the onMutate option to update your cache directly, or leverage the returned variables to update your UI from the useMutation result. Via the UI
adverse-sapphire
adverse-sapphireOP16mo ago
great this works thanks. do you know what is the common practise for mutations? I have a special file inside hooks folder like it's recommended for React development but I can't seem to make onSettled and onError callbacks work this way...
export const addUserMutation = () =>
useMutation({
mutationKey: ["addUserMutation"],
mutationFn: async (user: User) => {
const response = AxiosAPI.post(`/users`, { ...user });

return response;
},
onError: (err) => {
console.log("error inside hook file", err);
},
});
export const addUserMutation = () =>
useMutation({
mutationKey: ["addUserMutation"],
mutationFn: async (user: User) => {
const response = AxiosAPI.post(`/users`, { ...user });

return response;
},
onError: (err) => {
console.log("error inside hook file", err);
},
});
if I do it this way "onError" will log the error if it happens during the API call but if I do it inside a component I don't get any callbacks for the other hooks...
const onSubmit = handleSubmit(async (values) => {
try {
queryClient.setQueryData(["users"], (old: User[]) => {
const users = [
...old,
{
...values,
id: crypto.randomUUID(),
firstName: values.firstName,
lastName: values.lastName,
lastSignInAt: null,
status: values.status,
emailAddresses: [{ emailAddress: values.email }],
isMutating: true,
},
];

return users;
});

mutateAsync(values, {
onSuccess: () => {
console.log("here");
},
onSettled: () => {
console.log("SET");
},
onError: (err) => {
console.log("err", err);
},
});

router.push("/users");
} catch (error) {
console.log(error);
}
});
const onSubmit = handleSubmit(async (values) => {
try {
queryClient.setQueryData(["users"], (old: User[]) => {
const users = [
...old,
{
...values,
id: crypto.randomUUID(),
firstName: values.firstName,
lastName: values.lastName,
lastSignInAt: null,
status: values.status,
emailAddresses: [{ emailAddress: values.email }],
isMutating: true,
},
];

return users;
});

mutateAsync(values, {
onSuccess: () => {
console.log("here");
},
onSettled: () => {
console.log("SET");
},
onError: (err) => {
console.log("err", err);
},
});

router.push("/users");
} catch (error) {
console.log(error);
}
});
other-emerald
other-emerald16mo ago
Mastering Mutations in React Query
Learn all about the concept of performing side effects on the server with React Query.
other-emerald
other-emerald16mo ago
Simply it means your component got unmounted (because router navigation?) so callbacks on mutate or mutateAsync won't be called. This is by design You can add await to mutateAsync. So navigation will happen only after mutation has completed and callbacks will be called
adverse-sapphire
adverse-sapphireOP16mo ago
but if I add await then my component waits for the mutation to finish and only then it redirects back to users page... Thats the whole reason I wanted to make an optimistic update, to redirect immediately make a fake (optimistic) user record then on callback refetch the real data from the server in onSettled I ended up extracting this logic from new page and put it inside a dialog on the same page...
other-emerald
other-emerald16mo ago
There are 2 sets of callbacks: one for useMutation and one for mutate/mutateAsync. useMutation callbacks are always called, you can invalidate your query to fetch new data in there.
adverse-sapphire
adverse-sapphireOP16mo ago
yes but I have queries outside of my Vue application (different file so I can reuse hooks) so they don't have access to useQueryClient() hook - if I initialize a new query client it does not have cache for all of the previous queries.

Did you find this page helpful?