
No Comments Yet
Be the first to share your thoughts and start the conversation.
Be the first to share your thoughts and start the conversation.
Complete source code available till this point of 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
In this lesson, we focus on creating the user interface for the question details page, incorporating essential elements such as user information, question metrics, and layout styling. The discussion includes how to structure the layout responsively for different devices and implement components for avatars and metrics.
00:00:02 In this lesson, we'll develop the UI of the question details page.
00:00:07 Currently, we're just fetching its ID, and this is how the finished page should look like.
00:00:12 We should have the avatar and the name of the user that created that question, alongside some metrics for upvotes, downvotes,
00:00:20 and the ability to save it to our favorites.
00:00:23 The title, when the question was asked, the number of answers and views, the description alongside with a code block, tags,
00:00:31 and then finally, later on, we'll implement the comment section.
00:00:35 So, what do you say that we get started?
00:00:37 Back in the code, we can first head over to our root, and then go to questions ID.
00:00:44 Currently, it just says question page and renders the ID.
00:00:48 Within it, we can return just an empty React fragment, and within it, we can have a div to start creating our layout.
00:00:55 This div will have a class name equal to flex-start, w-full, and flex-column, so we can show the elements one below another.
00:01:05 As a matter of fact, let's show our screen side by side, so we can visually see what we're doing, at least on the mobile screen.
00:01:11 And then later on, we'll check how good it looks like on desktop.
00:01:14 So, if I take this and collapse it, and head over to our localhost 3000, you should be able to see just an empty screen with a navbar at the top.
00:01:24 Next, let's head within this div and let's create another div with a class name of flex-w-full, flex-call-reverse, and justify-between.
00:01:37 I'll explain this flex-call-reverse later on, but basically what it does is sometimes on full screen devices, on desktop,
00:01:45 you have some important information on the left, and other piece of more important information on the right.
00:01:51 But on mobile, that very important right-hand information would fall down below the screen.
00:01:56 So sometimes, even though both of these would be visible on desktop, here on mobile, we want to show that thing that comes later at the top.
00:02:04 And that is when you apply a flex call reverse.
00:02:06 Within that div, let's create another div that'll have a class name equal to flex, items-center, justify-start, and a gap of 1. And within it,
00:02:21 we can render a user avatar, which is a component we have already created before.
00:02:26 So to it, we can simply pass a couple of props, such as the ID of the avatar we want to render.
00:02:33 For now, I'll simply say author ID, like this, to fake it.
00:02:38 And for the time being, I want you to go below the lesson and then copy the fake question data that I put there.
00:02:45 and then put it above the question details component.
00:02:48 It'll look something like this.
00:02:50 It simply says, hey.
00:02:53 ID is Q123, we have the title, and then a very long question.
00:02:58 The reason why I wanted to have this question is so that we don't have to recreate it every single time.
00:03:03 We have it here, and we have some detailed code blocks within that question just to make sure that everything can nicely fit within it,
00:03:10 alongside with the number of upvotes, downvotes, views, answers, and more.
00:03:15 And of course, we have the information about the author.
00:03:17 So just copy it, add it here at the top, and then collapse it.
00:03:22 We'll of course remove it later on once we start using real data.
00:03:26 But for the time being, we can say for the user avatar ID, we can give it the sample question dot author dot ID, or I think that's the dot underscore ID.
00:03:38 We also need to give it a name which will be equal to SampleQuestion.Author.Name as well as a class name equal to Size-22 and a fullback class name which
00:03:53 we'll implement soon equal to Text-10px.
00:03:57 So now if you save this, you should be able to see a very small looking avatar right here.
00:04:02 And just so we don't have to repeat ourselves by saying sample question every single time, we can actually destructure those properties right here at the
00:04:10 top by saying const, and then destructure the author from the sample question.
00:04:16 And then it's just going to look like this.
00:04:18 Author, ID, and name.
00:04:21 And later on, we can destructure all of the other fields which we'll use, such as created ad, when the question was created at.
00:04:29 Then we have the answers that belong to that question.
00:04:34 We also have the number of views that belong to the question.
00:04:38 And then, of course, the tags which we want to map over.
00:04:41 These are the most important things for now.
00:04:43 Now, as you can see, this user avatar is obviously way too small.
00:04:48 So, let's head over into the user avatar component.
00:04:51 And you should be able to notice that we're not really passing any kind of information to it of how big we can make it.
00:04:56 So, right here at the top, let's also accept the fallback.
00:05:00 class name, which is optional of a type string.
00:05:04 And then right here for the avatar fallback, we can turn it into a dynamic CN class name, which will look something like this.
00:05:12 Make sure to properly close it as well.
00:05:14 CN has to be imported from lib utils.
00:05:17 And then alongside these general class names, we can also provide a fallback class name, which we're now passing through props.
00:05:25 So we can accept it right here through props.
00:05:29 which will make it even smaller now because we're passing the text of 10 pixels right here, and we haven't wrapped the 22 pixels in square parentheses
00:05:38 to make sure that it is a unique value.
00:05:40 And if we compare with the design, I think it'll be okay.
00:05:44 We can modify it further now that we made our user avatar more reusable than before.
00:05:48 Now, let's head below the user avatar, and let's render a link component coming from Next Link.
00:05:54 That'll have an href equal to routes.
00:05:58 Make sure to import it from constants routes.profile.
00:06:01 And then we have to point to the author underscore ID, which we're getting from the dummy data.
00:06:06 Within that link, we can render a p tag that'll have a class name equal to paragraph.
00:06:16 And within it, we can render the author.name.
00:06:26 There we go.
00:06:27 Jane Doe.
00:06:28 Now we can head below the link and then below one more div, and there we can start creating the second part of the layout.
00:06:36 which will be a div that'll have a class name equal to flex and justify-end.
00:06:43 And within it, we can just say p of votes.
00:06:47 Okay, that's the number of votes that this question has.
00:06:51 Let's go below that div and below one more div and render the H2 that'll have a class name equal to H2 dash semi-bold, text dash dark 200, light 900, margin
00:07:06 top of 3.5, and W full.
00:07:09 And within it, we can render the question title, which should look something like this.
00:07:15 And I believe we also have it under sample question dot title.
00:07:20 how to improve React app performance.
00:07:22 That's better.
00:07:23 Now, let's head below this H2 and below one more div, and we'll create a new div that'll keep track of all of our metrics.
00:07:32 So, I'll give it a class name equal to margin bottom of 8, margin top of 5 to divide it a bit from the div above, flex, flex-wrap,
00:07:43 and a gap of 4. And within it, we can render a metric component, which we created before, coming from components metrics.
00:07:52 It needs a couple of params.
00:07:53 We already know that, right?
00:07:55 It needs an image URL, an alt tag, value, title, href, and more.
00:08:00 So let's just provide those that are mandatory.
00:08:03 That'll be an image URL.
00:08:04 And this time, we'll say when it was published.
00:08:07 So maybe it can be forward slash icons, forward slash clock dot SVG to indicate when it was posted.
00:08:15 an alt tag can be clock icon value can be something like a template string of empty space and then asked and then we can say get timestamp this is our
00:08:31 utility function and to it we can pass a new date which will create based off of the created at property of the sample question,
00:08:43 which we destructured.
00:08:44 And there we go.
00:08:45 You can see asked five days ago.
00:08:47 We can also give it a title, which can be just an empty string, as well as text styles, which will be equal to small regular.
00:08:57 Text-dark 400, light 700. And now it's barely noticeable.
00:09:03 But you can see we want to make some changes to this.
00:09:05 We want to style it further.
00:09:07 And that means that we have to make this component a bit more reusable.
00:09:11 Because think about it, when did we create this component before?
00:09:14 Well, we did it for the question card, not for the question details page.
00:09:18 So now let's modify it a bit further so it looks good on question details too.
00:09:24 Let's head over into the metric component.
00:09:26 And now let's search for the title.
00:09:29 See, before we were always rendering the title, but now we want to render the title only if it exists.
00:09:35 So we can say if title exists, then render the span with the title.
00:09:40 else simply render a null also we can accept a new prop called title styles so we can say title styles is optional of a type string but we can define the
00:09:52 prop type right here title styles optional of a type string and then we can use it right here on that title specifically on the span,
00:10:01 where we can turn this class name into a CN coming from LibUtils.
00:10:07 We can always render the small regular line clamp one, and then we can remove the rest of this, and simply render the title styles if necessary.
00:10:16 And we also have to make sure that this metric component is now looking good wherever we're using it.
00:10:22 And I believe that is on the home page.
00:10:24 Yep, see how it looks right here?
00:10:26 So let's head over to the question card and find where we're displaying different metrics.
00:10:32 And we can add title styles of MaxSM.
00:10:37 Hidden.
00:10:38 This will just ensure that it looks a bit better on mobile devices.
00:10:41 We don't need to show as two weeks ago here because we already have two weeks ago here on the top for mobile devices.
00:10:48 And I think that's it.
00:10:50 Now we can head back over to our question details page and we can add all the other metrics as well.
00:10:56 I can do that by simply duplicating it two more times.
00:11:01 And instead of a clock, we'll show a message.svg.
00:11:06 It'll be a message icon.
00:11:07 And we can render a different value right here.
00:11:10 In this case, the value will be the number of answers that people have left on this question.
00:11:15 So that'll simply be answers.
00:11:18 Let's also scroll down a bit and this time we'll render an eye icon.
00:11:22 So this is a eye icon.
00:11:25 And here for the value, we can simply render the number of views.
00:11:30 Now we want to somehow format that number of views.
00:11:32 So let's simply head over to utils and let's collapse everything we have here.
00:11:39 And below get times tab, we'll create a new function called export const format number, which takes in a number of a type number and it should return the
00:11:52 formatted version of that number.
00:11:54 So if the number is greater than or equal to one with six zeros, So that's greater than 1 million.
00:12:05 In that case, we can return that number divided by 1 million and then call the dot to fixed on it and append the letter M as in million to it.
00:12:18 We can also check if number is greater than 1000, we can append a K to it.
00:12:24 Else, if it's not, we can simply return that number to string to format it nicely.
00:12:30 So that'll end up looking something like this.
00:12:33 We have one, two, three, four.
00:12:35 And if we now wrap it in a format number function and pass over the number of views, it's going to say 1.2K.
00:12:43 Perfect.
00:12:44 Much better.
00:12:45 Finally, let's go below this div that's wrapping these metrics and let's render a p tag that'll say preview content.
00:12:54 And here we can render a div that'll have a class name equal to margin top of eight, flex, flex dash wrap, and the gap of two.
00:13:06 And here we can very easily map over our tags by saying tags.map, where we get each individual tag of a type tag.
00:13:18 And for each one, we can return a tag card, which will look something like this.
00:13:25 a regular tag card, which we can import from components, with a key, since we're mapping over it, equal to tag.underscoreID,
00:13:33 an underscore ID field equal to tag.underscoreID as a string, so we can let TypeScript know that there will be a string here,
00:13:42 a name equal to tag.name, and finally compact, to render the compact version of that tag, and now we can see React, Node,
00:13:50 and Postgres.
00:13:52 So right now, you can see that we have the top part, which is the user information, the title, the metrics, as well as the tags.
00:14:00 But what we're missing is the part in between, and that is the question content.
00:14:06 And since question content can contain multiple paragraphs and even code blocks, which we have to render properly, I decided to leave that for the next lesson.
00:14:15 So let's show the main part of our question, which is the question content itself in the next lesson.