
Join the Conversation!
Subscribing gives you access to the comments so you can share your ideas, ask questions, and connect with others.
Let's start by creating a new server action to create a review. We will use the Review model to create a new review.
We will open reviews.ts in the directory and add a new function called that will create a new review.
reviews.ts;
("use server");
export async function createReview() {
// todo: create a new review
}
Our next step is to decide what data we need to create a review.
Let's take a look at our model schema in the directory.
//
const authorSchema = new Schema({
name: { type: String, required: true },
email: { type: String, required: true },
});
const ReviewSchema = new Schema<Review>({
author: { type: authorSchema, required: true },
rating: { type: Number, required: true },
content: { type: String, required: true },
// connect to Product
productId: { type: Schema.Types.ObjectId, ref: "Product", required: true },
});
Based on the schema, we need the following data to create a review:
- The author of the review, this is an object.
- The rating of the review.
- The content of the review.
- The ID of the product that the review is for.
Conveniently, because we named our interface , we can use the same import to describe our type for the argument in the function.
//
import Review from "@/lib/models/review";
export async function createReview(review: Review) {
// todo: create a new review
}
This means that we can pass an object that matches the interface to the function:
example
const newReview = {
author: {
name: "John Doe",
email: "john@email.com",
},
rating: 5,
content: "This is a great product!",
productId: "123456",
};
createReview(newReview);
Now, using what we've done in the previous lessons, we can create a new review using the model.
//
export async function createReview(review: Review) {
await dbConnect();
try {
const newReview = await Review.create(review);
return newReview._id.toString();
} catch (err) {
console.error(err);
throw new Error("Error creating review");
}
}
In this function, we first connect to the database using . We then use the model to create a new review using the data passed to the function.
If the review is created successfully, we return the ID of the new review. If there is an error, we log the error and throw a new error.
Let's take a look at the "AddReview" form. We can find it in: file.
This is the submit handler for the "AddReview" form:
//
const handleSubmit = (event: any) => {
event.preventDefault();
console.log({ name, rating, review });
};
Just like before, we'll need to modify this function to call the action we just created.
//
const handleSubmit = async (event: any) => {
event.preventDefault();
try {
const newReview = {
author: {
name: name,
email: "example@email.com",
},
rating,
content: review,
};
const reviewId = await createReview(newReview);
console.log("Review created with ID: ", reviewId);
} catch (error) {
console.error("An error was thrown: ", error);
}
};
Now we're sending the author, rating, and content to our server action. But we're missing the field. We need to pass the to the action.
This component doesn't currently have access to the field. We need to pass the to the component.
If you head over to the page that renders the component, you'll see that the is a prop we get from our params.
We can pass the prop to the component.
Open up and pass the prop to the component.
page.tsx
...
<div classname="md:col-span-2">
<AddReview id={id} />
</div>
Now that we have access to the in the component, we can pass it to the action.
AddReview.tsx
export default function Component({ id }: { id: string }) {
...
const handleSubmit = async (event: any) => {
event.preventDefault();
try {
const newReview = {
author: {
name: name,
email: "example@email.com",
},
rating,
content: review,
productId: id,
}
const reviewId = await createReview(newReview);
console.log("Review created with ID: ", reviewId);
} catch (error) {
console.error("An error was thrown: ", error);
}
};
}
You've done it! You've successfully created a new Review in the database. But what about the caching?
In a previous lesson, I mentioned that we can revalidate the cache after creating a new Review. This will let us see the new Review immediately after creating it, and it will even update our average rating in real-time.
To do this we need to do two things:
Let's start by updating the function in the file.
// import the cache function
import { unstable_cache as cache } from "next/cache";
reviews.ts;
// rename the function to indicate we don't call it directly
async function _getReviewsAndRating(productId: string) {
await dbConnect();
const reviews = await Review.find({ productId });
const averageRatingResult = await Review.aggregate([
{ $match: { productId: new mongoose.Types.ObjectId(productId) } },
{ $group: { _id: null, average: { $avg: "$rating" } } },
]);
const averageRating = averageRatingResult[0]?.average || 0;
return { reviews, averageRating };
}
// export the cache function
export const getReviewsAndRating = cache(
_getReviewsAndRating,
["getReviewsAndRating"],
{
tags: ["getReviewsAndRating"],
revalidate: 60,
},
);
// import revalidateTag
import { unstable_cache as cache, revalidateTag } from "next/cache";
reviews.ts;
export async function createReview(review: Review) {
await dbConnect();
try {
const newReview = await Review.create(review);
// revalidate the cache
revalidateTag("getReviewsAndRating");
return newReview._id.toString();
} catch (err) {
console.error(err);
throw new Error("Error creating review");
}
}
Now, when we create a new Review, the cache will be revalidated, and we will see the new Review immediately!
You've successfully created a new Review and updated the cache. Great job!
But there's something missing! We're only passing in the name of the author and a placeholder for the email. We should be passing in the email as well.
Can you add a field for the email in the component and pass it to the action?
In this lesson, you learned how to create a new Review using the model. You also learned how to update the cache after creating a new Review.
To wrap up- we'll be developing for all of our products in the coming lessons.
But first we'll need to discuss aggregates in more detail. So far we've just used them to calculate the average rating. But there's so much more we can do with them, from easier pagination to including other models in our queries.
I'll see you in the next lesson!
"Please login to view comments"
Subscribing gives you access to the comments so you can share your ideas, ask questions, and connect with others.