Skip to content

Mutations

Mutations are used to modify or update data on the server, typically triggered by a user action, like submitting a form.

Creating a new tRPC Router

ts
import { z } from "zod";
import { publicProcedure, router } from "../trpc";

// Zod validation schema. You can use any validation lib or method.
// Check tRPC docs -> https://trpc.io/docs/server/validators#library-integrations
const deleteProduct = z.object({
  productId: z.string().min(2),
});

export const productsRouter = router({
  delete: publicProcedure
    .input(deleteProduct)
    .mutation(async ({ ctx, input }) => {
      // ctx has req and res. You can use it if required.
      // const { req, res } = ctx
      const { productId } = input;

      const product = await deleteProductById(productId);
      return product; // You need to return something.
    }),
});

The input will automatically be vaildated depending on your validation schema and appropriate error response will be returned, client side error handling is left up to the developer.

Usage in Client With React Query

tsx
import { trpc } from "@/lib/trpcClient";

const Products = () => {
  // Query which gives us the products.
  const { data: products } = trpc.products.getAll.useQuery();
  // Mutation use just created.
  const { mutate } = trpc.products.delete.useMutation();

  return products?.map((product, idx) => (
    <div key={idx}>
      <p>{product.name}</p>
      <span>{product.price}</span>
      <button
        onClick={() =>
          mutate({
            // ProductId is required or else it'll give type errors.
            productId: product.id,
          })
        }
      >
        Delete
      </button>
    </div>
  ));
};

export default Products;

Handling Loading States

For a good user experience you need to alert them if some mutation is pending.

tsx
const { mutate, isLoading } = trpc.products.delete.useMutation();

if (isLoading) {
  return <p>deletion is in progress...</p>;
}
// ...

tRPC just provides a type-safe wrapper around React Query, thus you've access to all the features of react query.

Handling Errors & Success

tsx
const { mutate, isLoading } = trpc.products.delete.useMutation({
  onError: (err) => {
    alert(err.message); // Better to use a toast instead
  },
  onSuccess: () => {
    alert('Deletion success'); // Better to use a toast instead
  },
});

if (isLoading) {
  return <p>deletion is in progress...</p>;
}
// ...