
Join the Conversation!
Subscribing gives you access to the comments so you can share your ideas, ask questions, and connect with others.
So far, we've learned how to write and implement the first two major CRUD operations: Create and Read. In this lesson, we'll learn how to implement the Update operation.
We've created a new Product, and learned to read the data and it's related models from the database.
In this lesson, we'll learn to create an update operation. We'll create a new server action that updates a Product in the database.
Let's open our file once more and create a new server action called .
products.ts;
export async function updateProduct() {
try {
// todo: implement the updateProduct server action
} catch (error) {
return null;
}
}
Think about what you need to update a Product.
Let's implement the with it's arguments and some basic error handling:
products.ts;
// use what we need to apply an update
// to decide what arguments we need:
export async function updateProduct(productId: string, data: Partial<Product>) {
try {
// TODO: Implement the updateProduct server action
} catch (error) {
console.error(error);
return null;
}
}
We use to allow us to update only the fields we want to update.
We're telling TypeScript that the object may only contain some of the fields of the model.
Mongoose provides a method called that we can use to update a document in the database.
The method takes three arguments:
Let's use this method to update the Product with the given with the given .
products.ts;
export async function updateProduct(productId: string, data: Partial<Product>) {
// Make sure we connect to the database
await dbConnect();
try {
const updatedProduct = await Product.findByIdAndUpdate(productId, data, {
new: true,
});
return updatedProduct._id.toString();
} catch (error) {
console.error(error);
return null;
}
}
The option tells Mongoose to return the updated document after the update operation is complete.
If you don't provide this option, Mongoose will return the document as it was before the update operation.
Now that we've implemented the server action, let's integrate it into our application.
Let's start in the file. This is the file that chooses which component to render based on the path, and the file that reads the Product data from the database.
We'll take a close look at what's happening here:
export default async function Page({ params }: { params: { path: string[] } }) {
// figure out the first and second part of the URL
const method = params.path[0];
const id = params.path[1];
// If the method is 'new', render the AddProduct component
if (method === "new") {
return <AddProduct />;
}
// Figure out which component to render based on the method
if (method === "edit") {
return <AddProduct edit id={id}/>;
}
if (method === "delete") {
return <DeleteProduct id={id} />;
}
// get the product by ID
const product = await findProductById((id));
// If the product is not found, return a message
if (!product) {
return <div>Product not found</div>;
}
}
Currently this is the way this component works:
We'll need the product's information to update it. So we'll need to pass the product data to the component when the method is .
Which means we need to update the component to accept the data as a prop, and we need to render that component AFTER we fetch the data.
Let's update the file to pass the product and correct the order:
page.tsx
export default async function Page({ params }: { params: { path: string[] } }) {
const method = params.path[0];
const id = params.path[1];
if (method === "new") {
return <AddProduct />;
}
const product = await getProductById(id);
const { reviews, averageRating } = await getReviewsAndRating(id);
if (!product) {
return <div>Product not found</div>;
}
if (method === "edit") {
return <AddProduct edit id={id} product={product} />;
}
if (method === "delete") {
return <DeleteProduct id={id} />;
}
...
}
Let's open the component used to display the Update form. In our case, it's the same as the component.
Let's reopen the file and it to use the product being passed into it now.
AddProduct.tsx
import Product from "@/lib/models/product";
...
export default function AddProduct({
edit,
id,
product,
}: {
edit?: boolean;
id?: string;
product?: Product;
}) {
...
}
Great! Now we're passing in the product. Our goal now is to make sure that the form is pre-filled with the product data when the method is .
Let's update the form states to pre-fill the data when the method is .
//
const [name, setName] = useState(product?.name || "");
const [price, setPrice] = useState(product?.price || 0);
const [description, setDescription] = useState(product?.description || "");
const [category, setCategory] = useState(product?.category || "");
const [images, setImages] = useState<string[]>(product?.images || []);
We're using the prop to pre-fill the form fields with the product data when the method is .
If the prop is not provided, we'll use the default values for the form fields.
Now that we've pre-filled the form with the product data, let's implement the update operation.
First, we'll import the server action in the component.
//
import { updateProduct } from "@/lib/actions/products";
Next, we'll implement the function to call the server action when the form is submitted.
We need to determine if the form is being used to create a new product or update an existing product. So we can use the prop to determine this.
//
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
try {
// if the method is 'edit', update the product
if (edit && product && id) {
const productID = await updateProduct(id, {
name,
price,
description,
category,
images,
});
router.push(`/product/view/${productID}`);
} else {
// else, create a new product
const productID = await createProduct({
name,
price,
description,
category,
images,
});
router.push(`/product/view/${productID}`);
}
} catch (error) {
// show some toast or alert to the user
console.error("Error creating product:", error);
}
};
We're using the prop to determine if the form is being used to create a new product or update an existing product.
If the method is , we call the server action with the product ID and the updated product data.
If the method is not , we call the server action to create a new product.
In this lesson, we learned how to implement the Update operation in our application.
We created a new server action called that updates a Product in the database.
We integrated the server action into our application by updating the file to pass the product data to the component when the method is .
We updated the component to pre-fill the form with the product data when the method is .
We updated the function to call the server action when the form is submitted.
In the next lesson, we'll learn how to implement the Delete operation.
"Please login to view comments"
Subscribing gives you access to the comments so you can share your ideas, ask questions, and connect with others.