@void-snippets/react

useMutations

create, update, and remove mutations with full TanStack Query results.

#What it does

Returns three mutation objects — create, update, and remove. When any of them succeeds or fails, TanStack Query automatically invalidates the cache for this resource, triggering a background refetch.

#Signature

typescript
contactHooks.useMutations(): {
  create: UseMutationResult<Contact.Detail, Error, Contact.Apis.Create>;
  update: UseMutationResult<Contact.Detail, Error, { _id: Contact.Id; payload: Contact.Apis.Update }>;
  remove: UseMutationResult<Contact.Detail, Error, Contact.Id>;
}

remove is the name (not delete) because delete is a reserved JavaScript keyword.

#Each mutation object

FieldDescription
mutate(variables)Fire-and-forget. Errors are silently caught unless you pass onError. Good for delete buttons.
mutateAsync(variables)Returns a Promise. Use with await when you need to wait before navigating or closing a modal.
isPendingtrue while the request is in flight. Use to disable and show a loading state on buttons.
isSuccesstrue after the last call completed successfully.
isErrortrue after the last call threw.
errorThe Error from the last failure.
dataThe server response from the last success.
reset()Return to idle state — clears isSuccess, isError, error.

#The key pattern — await before closing a modal

tsx
function ContactsPage() {
  const modal = useModal<Contact.Base>();
  const { create, update, remove } = contactHooks.useMutations();

  const handleSave = async (formData: Contact.Apis.Create | Contact.Apis.Update) => {
    try {
      if (modal.data) {
        await update.mutateAsync({ _id: modal.data._id, payload: formData as Contact.Apis.Update });
      } else {
        await create.mutateAsync(formData as Contact.Apis.Create);
      }
      // Only runs when the server responded successfully
      modal.closeModal();
      toast.success(modal.data ? 'Contact updated' : 'Contact created');
    } catch (err) {
      // Modal stays open — form fields are intact, user can fix and retry
      toast.error(err instanceof Error ? err.message : 'Something went wrong');
    }
  };

  return (
    <>
      <Button onClick={modal.openCreateModal}>+ New</Button>

      {list.map(contact => (
        <ContactRow
          key={contact._id}
          contact={contact}
          onEdit={()   => modal.openEditModal(contact)}
          onDelete={() => remove.mutate(contact._id)} // fire-and-forget — list auto-refreshes
        />
      ))}

      <ContactModal
        open={modal.isOpen}
        data={modal.data}
        isSaving={create.isPending || update.isPending}
        onSave={handleSave}
        onClose={modal.closeModal}
      />
    </>
  );
}