
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.
There’s a popular saying in the dev world—solve the problem first, then write the code. And that’s exactly what you did in the last lesson by breaking down the logic before jumping into implementation.
Now, it’s time to put that system logic to work and see the results in action. So… what’s next
Ask yourself—Where should we trigger this createInteraction function? For what kind of actions, and how?
Simple.
You’ll call it when a user:
All done?
Cool—let me show you how I’d approach it. (And yeah, there’s a small twist I added in my version… 😉)
Remember the next/after function we used earlier?
You first came across it when implementing view counts on the question details page. Back then, you learned how it executes only after a response is sent so it doesn’t block the UI. The great news? You can use this same after function inside server actions too.
And that’s exactly why I’m bringing it up again—because it’s a great fit for calling createInteraction.
Because tracking interactions like posts, votes, or views feels more like logging activity. Sure, there’s reputation logic involved, but these tasks shouldn’t block the core operation, like creating a question or casting a vote. The user shouldn’t have to wait for all that to finish—these things can run behind the scenes.
That means faster responses, snappier UI, and a better user experience overall (fast FCP and LCP 🚀).
So let’s use after to handle interaction logic post-response.
First, Call createInteraction when a user creates a question
export async function createQuestion(
params: CreateQuestionParams
): Promise<ActionResponse<Question>> {
// ...
try {
// ...
// log the interaction
after(async () => {
await createInteraction({
action: "post",
actionId: question._id.toString(),
actionTarget: "question",
authorId: userId as string,
});
});
await session.commitTransaction();
return { success: true, data: JSON.parse(JSON.stringify(question)) };
} catch (error) {
await session.abortTransaction();
return handleError(error) as ErrorResponse;
} finally {
await session.endSession();
}
}
Similar to this, call it when a user submits an answer
export async function createAnswer(
params: CreateAnswerParams
): Promise<ActionResponse<IAnswerDoc>> {
// ...
try {
// ...
// log the interaction
after(async () => {
await createInteraction({
action: "post",
actionId: newAnswer._id.toString(),
actionTarget: "answer",
authorId: userId as string,
});
});
await session.commitTransaction();
revalidatePath(ROUTES.QUESTION(questionId));
return { success: true, data: JSON.parse(JSON.stringify(newAnswer)) };
} catch (error) {
await session.abortTransaction();
return handleError(error) as ErrorResponse;
} finally {
await session.endSession();
}
}
Finally, it’s time to call it in the voting function.
This one’s slightly different. You’ll need to fetch the authorId first—basically, look up the question or answer the user is voting on and extract the author info. It’s a small extra step, but honestly, it’s a good one—it ensures we’re always tracking votes correctly and adds a nice safeguard to the process.
export async function createVote(
params: CreateVoteParams
): Promise<ActionResponse> {
// ...
try {
const Model = targetType === "question" ? Question : Answer;
const contentDoc = await Model.findById(targetId).session(session);
if (!contentDoc) throw new Error("Content not found");
const contentAuthorId = contentDoc.author.toString();
// ...
// log the interaction
after(async () => {
await createInteraction({
action: voteType,
actionId: targetId,
actionTarget: targetType,
authorId: contentAuthorId,
});
});
await session.commitTransaction();
session.endSession();
revalidatePath(`/questions/${targetId}`);
return { success: true };
} catch (error) {
await session.abortTransaction();
session.endSession();
return handleError(error) as ErrorResponse;
}
}
That’s all?
You’re free to keep extending this and call the action wherever it fits your use case—but you’ve got the idea now.
Catch you in the next lesson 👋
Complete source code for this lesson is available at