
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 To keep things clean, we'll create a separate component to display all the answers related to a question details page.
00:00:09 This component will include all the answers as well as the filtering and pagination for it.
00:00:15 So let's create a new file right within our answers folder.
00:00:19 So that's going to be here under components.
00:00:22 Create a new folder and call it answers.
00:00:26 And within Answers, create a new file called AllAnswers.tsx and run RAFCE.
00:00:35 After that, we can head over into our question details.
00:00:39 So that's going to be question ID, the same ones we edited a couple of last lessons.
00:00:44 And now we can scroll down below where we have the answer form.
00:00:49 We're going to create a new section right above that section.
00:00:53 and this section will have a class name equal to margin y of 5 to divide it a bit from the top and the bottom and here we can render the all answers component.
00:01:06 Make sure to import it and we'll pass over a couple of props such as data and within here let's just render the answers result question mark dot answers
00:01:18 That's going to be the actual data.
00:01:19 And then we can also pass the success, which will be equal to our answers loaded.
00:01:25 And finally the error, which will be equal to answers error.
00:01:30 We also need the number of total answers, which will be equal to answer result question mark dot total answers, or we can make it default to zero.
00:01:42 There we go.
00:01:42 Now we're utilizing all of those values that we're getting from the getAnswers action call.
00:01:48 So we can now head into our allAnswers component and accept those props.
00:01:54 So let's simply destructure the data, the success, the error, and the number of the total answers.
00:02:02 And I'll make it equal to the type of props, which we can define right here above.
00:02:08 by saying interface props will extend the action response.
00:02:15 And specifically, we want to fill it in with the answer data.
00:02:19 So we can say that it'll accept an array of different answers.
00:02:24 And don't forget, the action response itself simply means that we're going to have a success, data, error, and a status,
00:02:30 which is exactly what we're already accepting.
00:02:32 But this time, the data will actually contain the array of answers.
00:02:38 Pretty cool.
00:02:38 Some advanced TypeScript right here.
00:02:40 And also we can accept the total number of answers as a number.
00:02:44 Beautiful.
00:02:45 Now let's develop the UI.
00:02:48 I'll enter into one of these questions.
00:02:50 And instead of returning an empty div, I will return a div, but this time with a class name equal to margin top of 11 to create some space.
00:03:02 as well as an inner div that'll have a class name equal to flex items-center and justify-between.
00:03:12 And within it, I can render an h3 element that'll have a class name equal to primary-text-gradient.
00:03:21 and it'll render a number of total answers.
00:03:25 And then we can put a space and say answers.
00:03:28 So for example, one or two answers.
00:03:32 In this case, we get one answers.
00:03:34 Maybe we can fix this.
00:03:35 So if the number is one, in that case, we can remove the S.
00:03:40 Let's see if Copilot can do it for me.
00:03:42 I'll say if total answers is one, make it say answer instead of answers.
00:03:50 developers get too lazy nowadays, right?
00:03:53 It's so easy to get AI to do your job.
00:03:56 Let's see how quickly can it do it.
00:03:58 There we go.
00:03:59 It'll update it right now, generating the edits and the edits have been applied.
00:04:04 It is super simple.
00:04:05 If the total number of answers is one, then say answers, typically say answers.
00:04:09 I'll go ahead and accept this.
00:04:11 And now it says one answer.
00:04:14 Great.
00:04:15 Let's also render a p tag that'll simply say filters for now.
00:04:18 Later on, we'll make these filters real.
00:04:21 And now the question is, how are we gonna render the answers?
00:04:25 And a pretty cool thing here is that we will reuse our previous data renderer component to render the answers with empty and error states already managed.
00:04:36 For that, we'll have to create an empty placeholder text.
00:04:39 So let's modify the constants right here, states.
00:04:44 Remember, we had different situations for empty or error or so on.
00:04:49 But alongside empty questions or empty tags, I can actually duplicate this below.
00:04:54 And now I will say empty answers.
00:04:57 Then we can say no answers found.
00:05:01 And we can say something like the answer board is empty.
00:05:07 Make it rain with your brilliant answer that's going to be good and the text will be answer and we can route to home beautiful so now let's head back into
00:05:21 the all answers and let's render our data renderer below this div i'll render a self-closing data renderer component coming from dot slash data renderer
00:05:32 and i'll pass in the data of data error of error.
00:05:37 I'll also pass in success equal to success.
00:05:41 And let's not forget to pass an empty state or just empty, I think we called it, which will be equal to empty answers coming from components states.
00:05:52 And finally, and most importantly, we have to provide the renderer function where we get access to the answers or the data we're mapping over.
00:06:01 And then we can say answers.map And for each answer, we will automatically return a p tag that'll simply say answer card for now.
00:06:14 If I do this, you can see one answer card right here.
00:06:17 And of course, since we're mapping over it, we have to provide a key.
00:06:20 So I'll make the key equal to answer dot underscore ID.
00:06:25 The error is gone and we have one answer card.
00:06:29 If I go to the other question, like this one, we have zero answers right here.
00:06:34 And take a look at this.
00:06:35 We have this beautiful, no answers found board, make it rain with your answer.
00:06:41 And we even have a button.
00:06:43 Oh, but I think that shouldn't be the case.
00:06:45 Let me actually head over to states and let me remove this weird character at the end.
00:06:53 But now there shouldn't actually be a button.
00:06:57 So what would happen if I commented this out?
00:07:00 There we go.
00:07:01 That's good.
00:07:01 Because we can answer right below it, right?
00:07:03 So we don't actually need a button there.
00:07:06 Perfect.
00:07:07 So now that we have this, we are actually ready to develop the card UI.
00:07:12 So let's create a new component right here within cards, and let's call it answerCard.tsx.
00:07:21 run RAFCE right within it, and then import that answer card right here.
00:07:27 So whenever we're trying to return something, that something will actually be a self-closing answer card component, to which we can provide a key as before,
00:07:38 equal to answer.underscoreID.
00:07:41 And we can also spread all of the other answer data so we can nicely accept it within the answer card.
00:07:48 Let's do that right away by heading into it.
00:07:50 and destructuring all of the properties which we'll need, such as the underscore ID, maybe the author of the answer, the content,
00:07:59 and the created ad, and all those will be of a type, answer, just an interface of answer.
00:08:07 Now that we got that out of the way, let's actually return the UI of the answer card.
00:08:13 We'll wrap it in an HTML5 semantic article tag, And let's give it a class name equal to light dash border, border dash B,
00:08:26 as well as padding Y of 10. I'll save it and just say test within it for now.
00:08:33 So here we, of course, cannot see anything.
00:08:36 But if I head over to the data renderer question, or for you it's going to be another type of question, here we can see one answer and I can see test.
00:08:44 Great.
00:08:45 So now let's continue rendering the UI.
00:08:48 Within the article, I'll render a span element that'll have an ID equal to json.stringify, and then we're gonna pass over the ID,
00:08:58 and I'll give it a class name equal to hash dash span.
00:09:03 And it'll be a self-closing span element just like this.
00:09:08 What this does is simply applies a negative margin top and bumps it up by using the padding bottom.
00:09:14 It'll make more sense very soon.
00:09:17 Now let's head below this pan and let's render a div.
00:09:21 This is the div where all of the information about that specific answer will go.
00:09:26 Let's give it a class name, something like margin-bottom of 5, flex, flex-call-reverse, justify-between, gap of 5, on small devices flex of row,
00:09:44 on small devices, items-center, and on small devices, gap of 2. So we're immediately making it responsive.
00:09:52 Within it, I'll render another div.
00:09:54 This one will have a class name of flex, flex-1, items-start, and a gap of 1, and on small devices, items-center.
00:10:06 This div will contain all of the information about the user that posted that answer.
00:10:12 And we already have that, thankfully.
00:10:14 It's the user avatar.
00:10:16 So let's simply import it from dot dot slash user avatar.
00:10:20 And to it, we can pass some props such as the ID equal to author dot underscore ID.
00:10:29 We can pass a name equal to author dot name.
00:10:34 We can also pass an image URL equal to author.image.
00:10:39 And finally, we can pass a class name equal to, but let's first save it so we can see it.
00:10:45 There we go.
00:10:46 So now we can see a little JavaScript Mastery logo, or for you, it's going to be something else.
00:10:51 And let's give it a class name of size-5, rounded-full, object-cover, and max-color.
00:11:03 on small devices marching top of 0.5. And let's fix this rounded-dash-full typo mistake.
00:11:10 And there we go.
00:11:11 We have a very little logo right here.
00:11:13 That's because the user's logo doesn't matter.
00:11:15 What they have to say matters.
00:11:18 But below the user avatar, we can also render a link.
00:11:22 And this link, of course, has to be imported from next link.
00:11:27 And to it, we can pass an href of routes coming from constants dot profile.
00:11:36 And we have to pass the ID.
00:11:38 So in this case, that'll be equal to author dot underscore ID.
00:11:44 We can also give a class name to this link equal to flex flex dash call on small devices flex dash row and on small devices items dash center.
00:11:57 And within this link, we can render a p tag that'll render the author dot name.
00:12:03 Or if we don't have the author name, we can say something like anonymous, but that really shouldn't happen.
00:12:08 In this case, it says Adrian JS Mastery.
00:12:11 Let's give this p tag a class name of body dash semi bold text dash dark 300 light 700. And there we go.
00:12:22 Let's also see this margin top right here.
00:12:25 Maybe we don't need it.
00:12:27 Now, looks like this centers it properly.
00:12:29 So you can remove that last class name from the user avatar.
00:12:32 Now, below this P tag, I'll have another P tag.
00:12:36 And this one will render a span, inside of which we can render a dot.
00:12:42 You can just Google for a dot copy character to get it.
00:12:47 And then you can give it a class name of Max-SMHidden.
00:12:51 So we're going to hide that dot on smaller devices.
00:12:54 but in this case we're going to say span and then after a span we can say answered and then an empty space and then after that we can get the timestamp
00:13:06 from libutils and we'll pass in the created ad.
00:13:10 That'll look something like this.
00:13:12 Adrian answered one week ago in this case, or for you, it could be a couple of minutes ago.
00:13:17 Now we can style this b tag by giving it a class name of small dash regular text dash light 400 underscore light 500 margin left of 0.5. margin top of 0.5,
00:13:35 and a line clamp of one to ensure that it doesn't go below one line.
00:13:40 There we go.
00:13:41 And now we might need to bring back this property which we removed, which is on MAX SM devices.
00:13:47 margin top of 0.5 because now it'll center it a bit better with the logo.
00:13:53 We can even do maybe one or two.
00:13:56 Yep, I think two looks the best.
00:13:58 And also maybe on the link we can apply a max SM margin left of one to divide it a bit from the logo.
00:14:06 There we go.
00:14:07 This is now looking great.
00:14:08 Finally, we can go below the link and below one more div and create another div.
00:14:14 that'll have a class name equal to flex and justify dash end.
00:14:20 And here we can render the votes functionality for the answer.
00:14:25 You know, the answers are not pretty useful on their own.
00:14:28 What makes them useful is them being useful.
00:14:31 And how can we know if they're useful to the other people?
00:14:34 Well, by allowing people to vote, upvote or downvote to let other users know that they are indeed useful.
00:14:41 So soon enough, we'll implement the voting functionality.
00:14:44 But for now, our answer is missing the most important part.
00:14:47 And that is going to happen right here below the votes and then below one more div where I'll render a preview component.
00:14:56 preview coming from editor preview, which is actually our markdown editor.
00:15:01 So to it, we have to pass the content equal to content coming from the props.
00:15:07 If we do this properly, you'll see the data or the content of that specific answer completing our answer card.
00:15:16 Beautiful.
00:15:17 It looks great on mobile.
00:15:18 You have the question and you have the answer right here.
00:15:21 And it looks even better on desktop.
00:15:24 How does the data renderer component work?
00:15:26 Well, here is your answer.
00:15:28 Beautiful.
00:15:29 While this approach works, it still blocks the UI.
00:15:33 Users must wait for all the answers to load before seeing the question details.
00:15:37 And although it's server-side, which means that they can receive the full HTML document at once, this increases the server execution time and the app's
00:15:46 first contentful paint.
00:15:48 We could further optimize it by using the Next.js streaming features, which loads each component independently for a better experience.
00:15:57 We'll explore optimizations in Next.js in a whole separate module where I'll teach you how that works in detail.
00:16:04 With that in mind, there we have it.
00:16:06 We have implemented the answers display.
00:16:09 So even if there are no answers, we have conveniently reused our data renderer.
00:16:14 And then if there are some answers, we can show those answers one after another.
00:16:19 along with the information of the user that answered them.
00:16:22 And this even leads to their profile.
00:16:24 So back into the code, I'll head over to our source control, and I'll say something like display all answers, commit and sync.
00:16:32 And this brings us to the end of the answers module.
00:16:36 But the next module builds right on top of this one, because we'll dive right into the ability to generate your AI answers.
00:16:45 Yep, that is right.
00:16:46 No longer do you have to type everything manually.
00:16:49 We'll implement the generate AI answer functionality to speed up the way in which you can help other people answer their questions.
00:16:56 So, let's do that next.