
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.
Complete source code for this lesson is available at
By logging in, you'll unlock full access to this and other free tutorials on JSM Pro.
Why? Logging in lets us personalize your learning experience, track your progress, and keep you in the loop with new workshops, coding tips, and platform updates.
You'll also be the first to know about upcoming launches, events, and exclusive discounts.
No spam—just helpful content to level up your skills.
If that sounds fair, go ahead and log in to continue →
Enter your name and email to get instant access
##Looks like we found a thief monkey By the way, I liked the trick how you reached till here. You have a good sense of humor. You will improve a lot if you join our course with this passion.
var
(function-scoped, outdated)let
(block-scoped, modern and recommended)const
(block-scoped, cannot be reassigned)_
, or $
let let = 5;
is invalid)myVar
and myvar
are different)string
, number
, boolean
, null
, undefined
, bigint
, symbol
Objects
, Arrays
, Functions
Subscribing gives you access to a brief, insightful summary of each lecture to stay on track.
00:00:02 Before we create an action that submits an answer to the question, let's think about exactly which values we'll need to make this action work.
00:00:10 As you know, those values are typically used within validations and types.
00:00:14 So head over to types.d.ts, or specifically action.d.ts, and create a new interface called create answer params.
00:00:26 And this one will just contain the question ID so we know where to submit that answer as well as the content of the answer.
00:00:34 And we'll have to do something similar for the schema.
00:00:36 So head over to validation.ts and export const a new answer server schema.
00:00:45 equal to answerSchema.extend, and we're going to extend it with a question ID, which is going to be a z.string required.
00:00:57 Now, keep in mind that in both our type and validation, we don't specify anything related to the user, nothing at all, as we'll get it directly through
00:01:07 the auth session on the backend side through our custom action handler.
00:01:11 Remember, the one called handlers, action.ts.
00:01:16 This one automatically gives us access to the session of the currently logged in user.
00:01:20 We can set the authorized to true while calling this handler, which will ensure that the user has to be authenticated and authorized to submit an answer.
00:01:28 And at the same time, it'll make it return the ID of that user.
00:01:33 This way our application is more secure with tight validations and we don't have to pass props everywhere to make it work.
00:01:40 Let me show you what I mean.
00:01:41 I'll create a new file within lib actions, and it'll be called answer.action.ts.
00:01:49 As you know, each action has to start with use server.
00:01:52 Don't forget this, you'll get some nasty errors.
00:01:55 And then we can export a new async function by saying export async function, create answer, which accepts params of a type create answer params.
00:02:10 And it returns a promise, specifically an action response of the iAnswerDoc.
00:02:19 So it has to return the document in the database that matches the answer schema.
00:02:24 And within it, we can call our special action const validation result.
00:02:31 is equal to await action coming from handlers.
00:02:35 And now we can pass over all the params, the schema of a type answer server schema, as well as authorize is set to true,
00:02:46 which will ensure that we have to be authorized and the validation result will therefore include the ID of the currently logged in user.
00:02:53 Now, right below it, let's check if a validation result is an instance of error.
00:03:04 And if it is, we're going to return handle error coming from handlers to which we're going to pass the validation result and say that it'll look as the
00:03:15 error response.
00:03:16 So our TypeScript knows what to expect.
00:03:19 After that, we can extract the content and the question ID by saying const content and question ID coming from validation result dot params.
00:03:31 And we can add an exclamation mark to let it know that we know that it'll be there.
00:03:35 And the second piece of the puzzle is to get to know the user ID of the user that's creating that answer.
00:03:42 So we can make it equal to validation result, question mark dot session, question mark dot user, question mark dot ID.
00:03:50 As you know, we want to make this an atomic action.
00:03:53 So let's actually start a new session.
00:03:55 This is not regarding the user session, but actually a mongoose session.
00:04:01 So we can say await mongoose.startSession.
00:04:05 So if something goes wrong when it comes to submitting an answer, we can just very easily stop that entire transaction from happening.
00:04:13 And we can say session.startTransaction.
00:04:17 Don't forget to import mongoose from the top, coming from mongoose right here.
00:04:24 import mongoose from mongoose.
00:04:29 Perfect.
00:04:30 And now we can open up a try and catch block.
00:04:34 In the catch, we'll simply check if there's an error and abort the transaction.
00:04:40 We can do that by saying await session.abortTransaction and return a handle error as errorResponse.
00:04:47 And we can also have a finally where we can end the session either way.
00:04:51 So session.endSession because we have reached our goal.
00:04:56 But now in the try, we have to check if the question exists, the question we're trying to provide the answer to.
00:05:03 So let's say const question is equal to await question dot find by ID.
00:05:11 And we can pass a question ID right to it.
00:05:14 Make sure to import question coming from at forward slash database and make sure to properly spell question ID right here at the top.
00:05:21 And we should be good.
00:05:22 Finally, if there is no question, throw a new error saying something like question not found.
00:05:30 But if there is, we are ready to create a new answer.
00:05:33 So let's say const destructure the response of new answer and make it equal to await answer.create And remember, if we want to track this transaction with
00:05:47 a session, we first have to provide an array of docs we want to create, even though it'll be just a single document.
00:05:54 So an array, and then after that array, as the second parameter, we can provide a session, so we can stop it if something goes wrong.
00:06:02 But in that array, we can show an object where we set the author to be equal to user ID, the question to be equal to question ID,
00:06:15 and finally the content to be equal to content.
00:06:18 that's going to return the new answer we just submitted so if there is no new answer we can throw a new error saying fail to create an answer but if it's
00:06:28 exceeded we want to update the number of answers attached to that question by saying question.answers plus equal to one and we want to save that question
00:06:38 by saying await question.save into the session we're done so we're ready to commit that transaction And we also want to re-validate the path by saying
00:06:49 re-validate path coming from next cache.
00:06:51 And specifically, we want to re-validate the question details page.
00:06:55 So that's routes coming from constants routes dot question.
00:07:00 And we want to pass in the question ID.
00:07:04 At the end of the day, we want to just return a success is set to true and pass over the data of the newly created answer.
00:07:13 But first we have to parse it and then stringify it and then wrap it right here as the new answer.
00:07:20 That's the best way to get it over to the front end side.
00:07:23 So this is it.
00:07:24 We're just creating a new answer and adding it to the question.
00:07:28 Thankfully, this action hook is giving us access to this session, so we don't have to manually pass it over.
00:07:36 Now to test this action in action, pun intended, we have to call this from the client side.
00:07:42 So let's head over to the questions details page.
00:07:45 So that's questions ID.
00:07:48 And let's pass a question ID to the answer form.
00:07:52 Question ID equal to, I believe it's just ID right here, or no, it's coming from the database.
00:07:59 So it's question dot underscore ID.
00:08:02 And now we can go into the answer form to accept that prop.
00:08:06 We can do that very easily by destructuring the question ID and setting it as question ID of a type string.
00:08:15 Now, instead of just using the regular as submitting, I want to teach you something new.
00:08:20 I want to teach you a use transition hook, the same one we used with the create question.
00:08:25 It allows us to improve performance in React apps.
00:08:28 The use transition hook enables state updates to occur in a way that is less blocking for user inputs.
00:08:35 resulting in a smoother experience.
00:08:37 A simple example of using it is to simply say start transition and then get the pending state of that transition and optionally provide some kind of a timeout.
00:08:46 You can read more about it by just Googling use transition in React, but in this case I'll teach you how to use it.
00:08:51 So instead of saying is submitting, we can basically remove this entire use state and instead say const.
00:09:00 destructure it, and let's call it isAnswering, maybe a bit more fitting, and start answering transition.
00:09:09 And it'll be equal to use transition coming from React.
00:09:15 Now we can replace the submitting with isAnswering.
00:09:19 Make sure to spell it correctly at the top, isAnswering.
00:09:22 And now right here in the handle submit, we can call the server action we created.
00:09:28 by getting its result when calling it await create answer coming from lib actions, to which we have to pass the question ID as well as the content,
00:09:39 which is equal to values.content.
00:09:42 Finally, if a result is a success, in that case, we simply want to clear the form and render a toast with a title of answer posted successfully and a description
00:09:54 of something like your answer has been posted successfully.
00:09:58 Something like this.
00:10:00 Don't forget to import toast from UI toast, or in this case, it's coming from hooks use toast.
00:10:07 Also, we can do an else in case something goes wrong.
00:10:12 We can render a title of an error, a description of the error message.
00:10:17 So right here, let's render result.
00:10:21 dot error question mark dot message and a variant of destructive.
00:10:28 Perfect.
00:10:29 So now that we have our handle submit, we have to put it to use by putting to use the start answering transition.
00:10:36 The only thing we have to do is basically wrap the handle submit with it.
00:10:40 And we can do it right here at the start of the function itself.
00:10:43 start answering transition.
00:10:45 The first parameter is an asynchronous callback function, which looks something like this.
00:10:51 And then we can put the entire contents of what we wanted to do on the submit right within it, something like this.
00:10:58 So I just put everything within start answering transition.
00:11:01 Oh, and the question ID, it says that it doesn't exist.
00:11:05 Looks like I misspelled it.
00:11:07 Now it's good.
00:11:08 So let's actually test it out by creating an answer.
00:11:12 The question here is, how does the data renderer component work?
00:11:15 I just don't get it.
00:11:16 How does it know which data to accept?
00:11:18 And it magically returns tags, questions, or any other collection data.
00:11:22 Pretty crazy.
00:11:23 Well, we know how it works, don't we?
00:11:26 Well, if we head over to data renderer, You can see that it accepts some loaders and so on, but the main part it does is it takes the render function and
00:11:35 we pass the data to it.
00:11:36 So if we're calling it somewhere like data renderer, it works very similarly to how these frameworks render components, specifically React Native,
00:11:45 allowing you to just pass different states like success, error, data, empty, and so on, and then choose how you want to render them.
00:11:54 In this case, I can even ask Copilot to explain how this is working, explain how data renderer component works, and hopefully it'll provide me a nice enough
00:12:06 of an answer.
00:12:07 There we go.
00:12:07 It's also fixing my code for some reason, but I think this message itself should be good enough.
00:12:12 So I'll copy it just so I have some dummy content for our answer.
00:12:17 And you can also see how nice it is to have markdown ability as the answer because it's much easier to take a glimpse of that answer and immediately see
00:12:27 what is it about.
00:12:29 Pretty cool, right?
00:12:30 With that in mind, I can try to post an answer.
00:12:33 And it says posting, success, answer posted successfully.
00:12:37 The form didn't really clear.
00:12:39 That's okay.
00:12:39 We can clear it later on manually.
00:12:41 But for now, what I care about the most is that the answer actually got submitted.
00:12:45 You can see our revalidation worked because it says right now that we have one comment attached to this question.
00:12:52 But hey, how do we actually see it?
00:12:54 Because right now we're not rendering any of the answers on the question details page.
00:12:59 You can quickly head over to your MongoDB Atlas or take a look at your extension.
00:13:03 And if you do that, you'll be able to see that under the answers collection, we have one document.
00:13:10 with this specific ID and it attached itself to the author that posted it as well as to the question that it has been attached to.
00:13:18 And there is the contents of that question alongside the upvotes and downvotes set to zero and a created ad date set to the current time.
00:13:26 But what we care about as well is that we can see it in the question details.
00:13:32 So let's head over to our questions.
00:13:35 We have two documents.
00:13:36 We have to find the one that's not asking about the event loop, rather the one asking about the data renderer.
00:13:44 And now, if we take a look at the number of answers, it has been updated to 1. And that's all that we care about for now.
00:13:51 Later on, when trying to display the answers for each one of these questions, we'll be able to very easily retrieve them as each answer has an ID of the
00:14:00 question that it belongs to.
00:14:01 So let's commit this by saying, implement create answer action, and sync it.
00:14:07 And in the next lesson, we can display all the answers.