#Trying to aboid duplicating mutations for the same function call.

7 messages · Page 1 of 1 (latest)

pulsar copper
#

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>
    </>
  );
}

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?
viral dust
#

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>
        </>
    );
}
#

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

velvet igloo
#

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)
}
pulsar copper
velvet igloo
#

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.