T
TanStack9mo ago
xenial-black

Trying to aboid duplicating mutations for the same function call.

Hey there! I'm quite new to react query and I was wondering if there was a better way to achieve the following behaivour using a single mutation. First, lets see the code and then I'll explain:
export function Page() {
const [data, setData] = useState();

const submitMutation = useMutation({
mutationFn: updateData,
onSuccess: () => {
toast("Data saved");
navigate("/");
}
});

const saveMutation = useMutation({
mutationFn: updateData,
onSuccess: () => toast("Data submitted")
});

const handleMutation = ({ shouldNavigate }: { shouldNavigate: boolean }) => {
if (!shouldNavigate) {
saveMutation.mutate(data);
return;
}
data.state = 6;
submitMutation.mutate(data);
};
return (
<>
<form>...</form>
<Button
onClick={() => handleMutation({ shouldNavigate: false })}
>
{saveMutation.isPending ? "Saving..." : "Save"}
</Button>
<Button
className="font-semibold"
onClick={() => handleMutation({ shouldNavigate: true })}
>
{submitMutation.isPending ? "Submiting..." : "Sumbit"}
</Button>
</>
);
}
export function Page() {
const [data, setData] = useState();

const submitMutation = useMutation({
mutationFn: updateData,
onSuccess: () => {
toast("Data saved");
navigate("/");
}
});

const saveMutation = useMutation({
mutationFn: updateData,
onSuccess: () => toast("Data submitted")
});

const handleMutation = ({ shouldNavigate }: { shouldNavigate: boolean }) => {
if (!shouldNavigate) {
saveMutation.mutate(data);
return;
}
data.state = 6;
submitMutation.mutate(data);
};
return (
<>
<form>...</form>
<Button
onClick={() => handleMutation({ shouldNavigate: false })}
>
{saveMutation.isPending ? "Saving..." : "Save"}
</Button>
<Button
className="font-semibold"
onClick={() => handleMutation({ shouldNavigate: true })}
>
{submitMutation.isPending ? "Submiting..." : "Sumbit"}
</Button>
</>
);
}
This is just a mockup component, but imagine the following case: I have a form which values are represented by the data state. Under the form, I have two button actions, save and submit. They both call the same service function updateData (which makes and API call): * Save action: just calls the the function with data. * Sumbit action: calls the function with data after modifying data.state attribute and performs navigation I want to also render the buttons' text conditionally depending which action is being called. My question: Is there a simple way to achieve this without creating two different mutations like I'm doing there? Or am I already doing it the correct way?
5 Replies
xenial-black
xenial-black9mo ago
Something like this I think:
export function Page() {
const [data, setData] = useState();

const submitMutation = useMutation({
mutationFn: updateData,
onSuccess: (_data, { shouldNavigate }: { shouldNavigate }: { shouldNavigate: boolean }) => {
toast("Data saved");
if (!shouldNavigate) {
navigate("/");
}
}
});

const handleMutation = ({ shouldNavigate }: { shouldNavigate: boolean }) => {
if (!shouldNavigate) {
submitMutation.mutate({
shouldNavigate,
data,
})
}
submitMutation.mutate({
shouldNavigate,
data: {
...data,
state: 6,
}
})
};
return (
<>
<form>...</form>
<Button
onClick={() => handleMutation({ shouldNavigate: false })}
>
{saveMutation.isPending ? "Saving..." : "Save"}
</Button>
<Button
className="font-semibold"
onClick={() => handleMutation({ shouldNavigate: true })}
>
{submitMutation.isPending ? "Submiting..." : "Sumbit"}
</Button>
</>
);
}
export function Page() {
const [data, setData] = useState();

const submitMutation = useMutation({
mutationFn: updateData,
onSuccess: (_data, { shouldNavigate }: { shouldNavigate }: { shouldNavigate: boolean }) => {
toast("Data saved");
if (!shouldNavigate) {
navigate("/");
}
}
});

const handleMutation = ({ shouldNavigate }: { shouldNavigate: boolean }) => {
if (!shouldNavigate) {
submitMutation.mutate({
shouldNavigate,
data,
})
}
submitMutation.mutate({
shouldNavigate,
data: {
...data,
state: 6,
}
})
};
return (
<>
<form>...</form>
<Button
onClick={() => handleMutation({ shouldNavigate: false })}
>
{saveMutation.isPending ? "Saving..." : "Save"}
</Button>
<Button
className="font-semibold"
onClick={() => handleMutation({ shouldNavigate: true })}
>
{submitMutation.isPending ? "Submiting..." : "Sumbit"}
</Button>
</>
);
}
it's still a little jank since the data can be changed depending on the way your calling it. but hopefully it gets you on the right path
absent-sapphire
absent-sapphire9mo ago
You lose some baked in error handling, but another option is using mutateAsync which would mean you don't need to pass shouldNavigate to the mutation. We do that in some scenarios with shouldClose when our modals have "Save" and "Save & Close" buttons. Wild simplification, but it essentially is:
const result = await mutateAsync(...)

if (shouldClose) {
close(result)
}
const result = await mutateAsync(...)

if (shouldClose) {
close(result)
}
xenial-black
xenial-blackOP9mo ago
i kinda like that. in that case, for conditionally render the text in the buttons (since both have the same isPending state), i should create a new react state to know if the user is saving or is submitting right? or there is a smarter way to do that and avoid creating a new state?
absent-sapphire
absent-sapphire9mo ago
It's up to you how to implement your UX but we take a simple approach by leaving the text alone and disable via disabled={isPending} so a user can't spam click and create multiple mutations.
xenial-black
xenial-blackOP9mo ago
Thank you very much for your help, now I have a better idea on how to implement it

Did you find this page helpful?