
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
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.
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 Now that we're done with the question details and other users can leave their answers.
00:00:07 And by the way, look at this.
00:00:08 Looks like some text is overflowing the view on mobile, so we'll definitely have to fix that.
00:00:13 Good to know that it's happening because of this long answer.
00:00:16 But yeah, we'll get to fixing that very soon.
00:00:18 But for now, I want to focus on one more piece of this question details page.
00:00:23 and that is votes.
00:00:25 The ability to allow other people to vote on the quality of the question or even more importantly on the quality of an answer.
00:00:34 Just so I don't see this broken UI, I'll quickly head over to the second question which doesn't have any answers and this one is looking great.
00:00:43 So let's implement the voting UI.
00:00:46 We can do that by creating a new component within the components folder, and I'll put it in a new folder called votes.
00:00:53 And within votes, I'll create a file called votes.tsx.
00:01:00 There you can run RAFCE and we can import it where it's being used.
00:01:05 That's going to be within the question details.
00:01:07 So that's going to be the ID question, similar to before.
00:01:11 And here for now, I believe we only have a text that simply says votes.
00:01:16 I want to replace this p tag with an actual votes component coming from votes forward slash votes.
00:01:24 Nothing will change on the screen, but it should allow us to pass some props into it.
00:01:29 We want to pass the number of upvotes that we have on a specific question or answer.
00:01:35 So I'll say question.upvotes.
00:01:38 I also want to pass if the current user has upvoted.
00:01:43 So I'll say has upvoted and I'll make it true by default.
00:01:47 We also have to pass the number of downvotes, which will be equal to question.downvotes.
00:01:54 and finally we also need has downvoted and I'll make sure that both down and the up is lowercase letters so just voted is uppercased whatever you decide
00:02:05 is fine just make sure to stay consistent with it so has downvoted And hasUpVoted, by default hasUpVoted will be set to true and hasDownVoted will be set
00:02:17 to false.
00:02:18 Perfect.
00:02:19 Now we are ready to dive into the vote function and accept all of these params we're passing into it.
00:02:24 Or props to be more precise.
00:02:26 I'll accept upVotes, downVotes, hasUpVoted, as well as hasDownVoted.
00:02:34 And these will be of a type params.
00:02:39 or we can also call it props and at the top I'll say interface params and I'll take in the upvotes of a type number, has upvoted which will be of a type boolean,
00:02:52 downvotes number and has downvoted which will be set to a boolean.
00:02:58 And now we can start creating the UI for the votes.
00:03:01 We can do that by wrapping everything in a div that'll have a class name equal to flex-center and a gap of 2.5 in between the elements.
00:03:13 And within that div, I'll create another div that'll have a class name of flex-center as well as a gap of 1.5 in between the elements.
00:03:23 and the component itself will be super simple.
00:03:26 The only thing we have to do is render an image.
00:03:29 So let's import the next image and let's give it a source equal to.
00:03:35 If hasUpvoted is true, in that case we can go to forward slash icons forward slash upvoted.svg Else, we can go to forward slash icons,
00:03:46 forward slash upvote dot svg.
00:03:49 Of course, we have to give it a width and a height, so I'll make both equal to 18. And I'll give it an alt tag of upvote,
00:03:57 a class name equal to, I'll make it dynamic and say cursor dash pointer.
00:04:04 And if it is loading, in that case, we can also give it an opacity of 50 to indicate the loading progress.
00:04:13 Of course, this loading we haven't yet created, it'll be its own state.
00:04:16 So right at the top, I'll create a new use state snippet, call it is loading, set is loading, at the start set to false.
00:04:26 And of course we have to import react or rather use state from react.
00:04:31 As soon as you import use state, you actually have to turn this into a client rendered component, which is totally fine because this is just a small button.
00:04:38 So I'll just add a use client directive at the top.
00:04:43 And there we go.
00:04:43 You can see this little upvote at the top, right?
00:04:45 We can also give it an area label, which will make it more supportive of screen reading devices.
00:04:51 And I'll say upvote.
00:04:53 And we can also give it an onClick equal to a callback function where if not is loading, so we cannot upvote multiple times,
00:05:02 then we'll call a handleVote function.
00:05:07 And I'll pass a string of upvote.
00:05:10 So let's create this handleVote function by saying const handleVote.
00:05:16 And it'll be equal to an asynchronous function that takes in a vote type, which can be either an upvote.
00:05:25 for a downvote, and based off of it, we're ready to do some kind of an action.
00:05:32 Perfect.
00:05:33 So now that we have this upvote, we can also show the number of upvotes right next to it by rendering a div.
00:05:40 with a class name of flex-center, background-light700, underscore dark 400, min-w of 5, rounded-sm, and padding of 1. And within it,
00:05:56 we can render a p tag with a class name equal to subtle-medium, text-dark400, light900, and we can call our utility function of format number and pass
00:06:13 in the number of upvotes that we want to show right now equal to zero.
00:06:17 If you don't remember, the format number simply adds the letter K for thousands and letter M for millions.
00:06:24 So if we have 500,000 upvotes, it's just going to say 500K.
00:06:27 Perfect.
00:06:30 Now what we have to do is basically duplicate this entire div consisting of this image and this p tag right below.
00:06:41 And instead of upvote, this one will be suited for downvotes.
00:06:44 So I'll say has downvoted, icons downvoted and icons downvote.
00:06:51 The alt tag will be downvote.
00:06:54 The aria label will be downvote.
00:06:56 And once we call the handle vote, it'll simply pass a prop of downvote.
00:07:02 And when we're formatting the numbers, we'll say downvotes.
00:07:08 Perfect.
00:07:09 So now we can see up and down, and very soon you'll be able to choose which action you want to give to each one of these.
00:07:16 And also you can see that the upvoted has been set to true by default, so it has a green light, but we have two states for both of these arrows,
00:07:24 unclicked and clicked.
00:07:25 very soon we'll be able to toggle them on or off, as well as actually modify the number of votes that the question or the answer to that question have.
00:07:34 So while we're here, let's quickly implement this handleVote function.
00:07:39 First things first, to be able to vote, either upVote or downVote, we have to have access to the currently logged in user.
00:07:46 So a session must exist.
00:07:49 So to get to a session, we can just say const session, is equal to use session coming from nextAuthReact.
00:07:57 And then out of that session, we can extract the user ID by saying session.data?user?id.
00:08:07 Now that we have that, we can enter into the handleVote function and check if a user ID exists.
00:08:13 So we'll say if no user ID return, so exit out of the function, And we can also render a toast coming from Hook's Toast,
00:08:22 specifically a toast that'll have a title of something like, please log in to vote.
00:08:30 And we can also provide a description of something like, only logged in users can vote.
00:08:40 Okay, so now that we're past that rule, we can open up a new try and catch block.
00:08:46 In the catch, we'll simply show a toast that'll have a title of fail to vote with a description of something like an error occurred while voting.
00:08:56 Please try again later.
00:08:58 And we can put a variant equal to destructive.
00:09:02 And we can also do a finally.
00:09:05 So if either the try block or the catch block finishes, then we want to do something finally.
00:09:11 And that is set is loading to false.
00:09:14 So no matter what happens, we want to stop the loading.
00:09:17 Before we start voting, we actually want to turn on the loading so we know that something is happening.
00:09:22 And now we can actually do something right here within the try block.
00:09:28 And that something will be to form a success message.
00:09:32 So I'll say const success message.
00:09:35 is equal to and now if the vote type is equal to upvote then sure we could do something simple like say upvoted or downvoted but I want to do something
00:09:46 a bit different and say if the vote type is upvoted we can say upvote not has upvoted, then we can say successful or we can say removed successfully.
00:10:01 Hopefully this makes sense.
00:10:02 So if the action is upvote and if we haven't yet upvoted, then we're going to say upvote added successfully.
00:10:09 If it's upvote and we have upvoted, it's going to say upvote removed successfully.
00:10:14 And we can do a similar thing for the downvotes.
00:10:17 Once we do that, the only thing we want to do is render a toast.
00:10:22 That'll have a title equal to the success message and the description of your vote has been recorded.
00:10:28 And this is looking good to me.
00:10:31 Even though it seems like we're doing a lot in this function, nothing is yet happening because this data is not being registered anywhere.
00:10:38 Sure.
00:10:38 You can actually click here and remove it or add it, but you can see that's as far as you're going to get.
00:10:45 Now, we need the actual logic to increment or decrement the number of votes in our database attached to a specific question or an answer.
00:10:54 Before we do that though, let's quickly check the props we're passing into the votes function.
00:10:59 It seems like our tab script is letting us know that upvotes do exist, but downvotes don't seem to exist on the type question.
00:11:09 So let's actually head over into this question and let's see how does this question look like.
00:11:14 So it's saying that it'll return the action response of a type question that has ID, title, content, up votes.
00:11:22 But now if you take a look at it, it doesn't really have the down votes.
00:11:27 So we need to add it by saying down votes is of a type.
00:11:33 And you go back to where we're calling the votes, you can see it no longer complains and the votes component has everything we need.
00:11:41 So let's go ahead and push it by saying something like implement votes UI, commit and sync.
00:11:50 And in the next lesson, we'll implement a server action that'll update the number of votes for real.