
Join the Conversation!
Subscribing gives you access to the comments so you can share your ideas, ask questions, and connect with others.
"Please login to view comments"
Subscribing gives you access to the comments so you can share your ideas, ask questions, and connect with others.
It’s time to implement a feature that allows users to delete the questions they've created. So, how do you approach it?
Based on everything you've learned so far, the initial steps should feel familiar and pretty straightforward.
Here's what you'll do:
Yes — it's the same pattern you’ve probably followed a dozen times already in this course. Can you handle it now?
Here’s your solution:
In types/action.d.ts, define a new type called DeleteQuestionParams
interface DeleteQuestionParams {
questionId: string;
}
In lib/validations.ts, define a new validation schema called DeleteQuestionSchema
export const DeleteQuestionSchema = z.object({
questionId: z.string().min(1, "Question ID is required"),
});
In lib/actions/question.action.ts, create the skeleton for your new server action deleteQuestion
export async function deleteQuestion(
params: DeleteQuestionParams
): Promise<ActionResponse> {
const validationResult = await action({
params,
schema: DeleteQuestionSchema,
authorize: true,
});
if (validationResult instanceof Error) {
return handleError(validationResult) as ErrorResponse;
}
const { questionId } = validationResult.params!;
const { user } = validationResult.session!;
try {
// implement logic here
return { success: true };
} catch (error) {
return handleError(error) as ErrorResponse;
}
}
COOL. Now comes the most important part — the logic.
Before you start implementing anything, ask yourself a few questions:
1..2..3… should we start?
Yep — you’re thinking in the right direction.
Deleting a question isn’t just about removing one record. You’ve got to think about the ripple effects, edge cases, and related data. Here's a checklist to guide you:
Phew — a lot to cover, but trust me, it's manageable if you take it one step at a time.
Now go implement it
export async function deleteQuestion(
params: DeleteQuestionParams
): Promise<ActionResponse> {
const validationResult = await action({
params,
schema: DeleteQuestionSchema,
authorize: true,
});
if (validationResult instanceof Error) {
return handleError(validationResult) as ErrorResponse;
}
const { questionId } = validationResult.params!;
const { user } = validationResult.session!;
// Create a Mongoose Session
const session = await mongoose.startSession();
try {
session.startTransaction();
const question = await Question.findById(questionId).session(session);
if (!question) throw new Error("Question not found");
if (question.author.toString() !== user?.id)
throw new Error("You are not authorized to delete this question");
// Delete references from collection
await Collection.deleteMany({ question: questionId }).session(session);
// Delete references from TagQuestion collection
await TagQuestion.deleteMany({ question: questionId }).session(session);
// For all tags of Question, find them and reduce their count
if (question.tags.length > 0) {
await Tag.updateMany(
{ _id: { $in: question.tags } },
{ $inc: { questions: -1 } },
{ session }
);
}
// Remove all votes of the question
await Vote.deleteMany({
actionId: questionId,
actionType: "question",
}).session(session);
// Remove all answers and their votes of the question
const answers = await Answer.find({ question: questionId }).session(
session
);
if (answers.length > 0) {
await Answer.deleteMany({ question: questionId }).session(session);
await Vote.deleteMany({
actionId: { $in: answers.map((answer) => answer.id) },
actionType: "answer",
}).session(session);
}
// Delete question
await Question.findByIdAndDelete(questionId).session(session);
// Commit transaction
await session.commitTransaction();
session.endSession();
// Revalidate to reflect immediate changes on UI
revalidatePath(`/profile/${user?.id}`);
return { success: true };
} catch (error) {
await session.abortTransaction();
session.endSession();
return handleError(error) as ErrorResponse;
}
}
Did the use of session catch you off guard? Maybe, maybe not.
You’ll need to use session here to ensure atomicity — meaning, if the question is being deleted, then everything related to it should be deleted as well. It can’t be a situation where some database operations succeed, and others don’t. It’s all or nothing.
Lastly, call the deleteQuestion action inside the EditDeleteAction component, and make sure to test the functionality thoroughly.
"use client";
import ...
import { deleteQuestion } from "@/lib/actions/question.action";
import {...} from "../ui/alert-dialog";
interface Props {...}
const EditDeleteAction = ({ type, itemId }: Props) => {
const router = useRouter();
const handleEdit = async () => {...};
const handleDelete = async () => {
if (type === "Question") {
await deleteQuestion({ questionId: itemId });
toast({
title: "Question Deleted",
variant: "destructive",
description: "Your question has been successfully deleted.",
});
} else if (type === "Answer") {
// TODO: implement delete answer (deleteAnswer)
toast({
title: "Answer Deleted",
variant: "destructive",
description: "Your answer has been successfully deleted.",
});
}
};
return (
<div className="flex items-center justify-end gap-3 max-sm:w-full">
...
</div>
);
};
export default EditDeleteAction;
You did it. Onto next one 🚀
Complete source code for this lesson is available at
How did you manage to remove the blur property and reach here?
Upgrading gives you access to quizzes so you can test your knowledge, track progress, and improve your skills.
00:00:00 Okay, so how did you do?
00:00:02 We've done this a hundred times, but hey, it's totally okay that you failed on your first attempt.
00:00:10 It's one thing watching a video, and it's an entirely different thing trying to achieve something on your own.
00:00:16 So, if you struggled a bit on this simple task, I think that shows you exactly what we're trying to do with this, and that is to get you to do things on
00:00:26 your own.
00:00:26 Still, let me show you how it should have looked like.
00:00:31 Here, we have a delete question schema, where we just say that we want to accept a question ID.
00:00:37 We do a similar thing for the action.
00:00:40 We'll just say, hey, we're going to accept the question ID as the param into the delete question action.
00:00:46 Then we have to implement the action itself.
00:00:49 Delete question, we do the typical validations, we check for errors, we accept the question ID, as well as the user that's trying to delete that question.
00:00:59 We do all sorts of different checks, one of which is to check whether we are the author, so we actually have the right to delete it.
00:01:06 And then we also have to delete the related entries inside the transaction, such as the collections or tags connected with that question.
00:01:14 Once we do all of that, we remove all the votes, we clear it up, and finally, we delete the question itself.
00:01:21 It's not as simple as just saying questionGleed, but we have to also do some cleanup of tags, answers, votes, and more.
00:01:31 That's typically how it works in these more complex applications.
00:01:35 So hopefully that makes sense.
00:01:37 And finally, once you implement it, we're then ready to call it within our handle delete, within the edit and delete action,
00:01:44 where we just say, await, delete question, and pass the question ID as our first and only parameter, within an object, of course.
00:01:53 And why did I not call it question ID right here in the edit and delete action?
00:01:59 Well, that's because sometimes the item will not be a question.
00:02:03 It might be an answer.
00:02:04 So that's why I called it a generic item.
00:02:06 Perfect.
00:02:07 We're passing it and that should work.
00:02:09 So if you head back over to your profile, let's give it a shot.
00:02:13 I have the pagination question right here, which I'll try to delete, click continue, question deleted, and automatically the changes are reflected.
00:02:22 So, what do you think?
00:02:24 Let me know in the comments down below of whether you struggled a bit to create even such a simple action on your own or were you able to do it normally
00:02:34 as if you're watching and following along with me.
00:02:36 Let me know.