In this chapter, our goal is to implement comment reactions. We already have the comment item component, but in this chapter, we are specifically going to focus on this part right here. We will create both the UI for it, as well as the API part. We're going to start by defining the comment reactions schema. After that, we will create the reactions UI, which is this right here.
And then we're going to combine comment reactions schema with our comments get many procedure which will basically load the counts for each reaction like and dislike as well as the viewer reaction. So if you remember our video reactions, this is exactly what we did. We created video reactions and then we combined video reactions for get one video procedure so that we knew the counts and also the viewer reaction itself. So that's our goal for this chapter right here. And this is incorrect.
I left this from the previous chapter. No need for any infinite loading component. Great. Let's go ahead and start by building the comment reactions schema. Let's go ahead and go inside of our source database schema.ts And let's go ahead and find the comments.
And after comments, after all of these create, insert, create, update things, let's add export const comment reaction, comment reactions like this, pg table comment reactions. And inside, we're going to do a similar thing as our video reactions. So we will have the user ID, so you can copy that and add it into your comment reactions. And then we will have the comment ID, which will be a reference to comment ID and use the comments schema like this. Besides that, let's find another video reaction, for example, we will also have the type and these two timestamps.
So let's go ahead and copy these two and add them to our new comment reactions. There we go. Looks like the react reaction type is only defined here. So I'm going to cut this and I'm just going to add it here at the top of the file simply because it's something that we can reuse throughout other schemas. So yes, the reaction type is exactly the same in video reactions and it's gonna be exactly the same in our new comment reactions.
So instead of user and video ID we're gonna have user and comment ID and now we have to build the primary key so you can find video reactions again, and simply copy the second part here, go back inside of the comment reactions and add it here. And this will be called comment reactions primary key. And the second argument will be the comment ID. So instead of having a person, a third UUID, we'll just combine these two because they have to be a unique combination of one another. Great!
So this should be enough for us to do a push to the database. So let's do Bonnex Drizzle Kit Push and we should have no errors I believe. And after that we're going to open the studio and then we're going to add the relations so we can see why we are building those relations. There we go, change is applied. So that's enough for what we have to do for pushing it to the database.
But now let's go and visit our Drizzle Studio. Make sure to refresh the page so you can see the comment reactions. And now we have user ID, comment ID, and we have user and comment right here. But if I go inside of users, there we go. This is the problem.
This says ID comment reactions user ID. So it noticed that we have a new relation but it's not exactly sure how we wanna call that. So let's go ahead and use the relations API to make that a little bit prettier. And since it's so similar to video reactions, we can find the video reactions and simply copy video reaction relations. Find the comment reactions and below them paste the video reaction relations.
Rename them to comment reaction relations. Use the comment reactions schema And now we're going to reference one user, which comes from the comment reactions user ID, basically this field. And the other field will be a reference to comments. So this will be one comments, comments, my apologies, comments reactions, comment ID. And the reference here will be comments ID, comment reactions, like this.
There we go. And now that I look at this, so if you just save this and if you, let's go ahead and wrap it up actually. So besides this, we also need to find the user schema. So let's find the user schema. Let's find user relations.
And besides comments, we will also have comment reactions, many comment reactions. And we also have to find the comment individually. So let's find individual comment, find the comment relations and add reactions here to be comment reactions, actually many comment reactions and we have to import, I mean import destruct many from here and we can just call these reactions because I think it's redundant to call this commentReactions instead of a commentSchema. And now I just want to fix a few things here. So inside of my commentRelations, for example, when I define user, I use singular because it's one, but I use reactions because it's many.
It's a small thing, it really doesn't matter. But in my comment reaction relations, I don't do that. I say users, even though it's one. Let me change this to user, and let me change this to comment because there's only one user only one comment. The naming doesn't matter this is just an application level so it appears nice here and if you use the relational query then it will be useful for that but in this case let's go ahead and find if anywhere throughout our schema we are doing the same problem, let's call it that, video views.
Let's go ahead and look at video view relations. We do it right here. It's just one user, just one video here. Let's go ahead and check video reactions. Relations are right here.
Again, one user, just one video. Let me go ahead and just scheme through the rest of my code here. Comment relations, user video, many reactions. Video relations, one user, one category, views, reactions, comments, perfect. And let's see, videos are right here, category, videos, subscription, one viewer, one creator, all of that seems good.
And user relations, many videos, many video views, many video reactions, many subscriptions, many subscribers, many comments, many comment reactions. I think all of that is Great. So now what I'm going to do is I'm just gonna shut down the Drizzle Studio. If you want to, you can try pushing again. And again, most of the time I'm doing this is just to prove again that this Relations API is just an application level.
Again, no changes detected. So let's just try Digital Kit Studio and make sure you refresh the studio. And now, if you go inside of your users, you should have a nice comment reactions here. Like the same way you have video reactions here, here you have comment reactions. And if you go in the individual comment, you will just have reactions here because we are calling them reactions, no need to call them comment reactions.
And it should be consistent with our video. If I go inside of my videos here, I should have reactions here as well which are actually video reactions. Great! So we have that. If you want to, you can be more specific.
You can call them video reactions here and video reactions here if you want that. Great! So now that that is ready, let's go ahead and let's create the UI by visiting our modules, comments, UI, comment ID. And now what I want to do is I want to build this part right here. So I don't want to do anything else before we build this.
So let's replace this to do with a div. And let's give it a class name of FlexItemsCenter, gap 2, and margin top of 1. Inside, let's go ahead and open a new div with a class name and flex-items-center And then inside of here, we're going to add a button We should already have this imported from ShadCN components Let's give it a class name, my apologies, of Size8. And we're going to have some more attributes here, so let me just collapse this. Besides the class name, we're going to have a size of icon, a variant of ghost, and disabled for now, false.
And let's pass in the onClick here. Let's go ahead and add thumbs up icon from Lucid React. And let's go ahead and just prepare a dynamic class name. So I'm going to import CN from Libutilus because depending on whether we like this or not, we're going to show different things. So import CN and import thumbs up icon from Lucid React.
And now you can go ahead and copy this button and paste it below this. Let me just go ahead and see what we have now. I'm going to refresh this page. And you should have two like buttons right here. Now let's go ahead and add a count between each of these buttons.
So after the thumbs up icon add a span and simply render the number zero and give the span a class name of text extra small and text muted foreground. And you can copy this and add one after the button right here. And this will be thumbs down icon from Lucid React as well. There we go. So this is how it's going to look like.
Now let's go ahead and let's actually combine our new comment reactions schema inside of our comment procedure. So let's go inside of module, comments, server, procedures, and let's go ahead and let's find getMany. And what we have to do here is similar to what we had to do inside of video procedures, specifically inside of getOne. We have to find the viewer reaction, so we know what reaction the viewer made for that comment. And we have to also create counts for likes and for dislikes.
So let's go ahead and first add the counts. So we have this promise of total data which we use to display the count and we have the data. So you have to focus not on the first query inside of the promise all but instead in the second query right here. So let's go ahead and after the user add like count to be database, count comment reactions, which you can import from the database schema. Let me just align this.
So comment reactions. And then we're going to add and equals comment reactions type must be like and comment reactions comment id must match the comments ID which we are going to query by, which in this case will be this right here. So only the comment which we find will be used in this subquery here. So that's how we will automatically pass the needed comment ID. So now if you hover over the data here, you should have the like count for that comment.
And now you can go ahead and copy this and add the query below. Dislike count and simply change this to dislike. There we go. Now you can revisit your comment item component and find where we mock the number of likes and dislikes. So this is thumbs up icon.
So this should now be comment.likecount. And this should be comment.dislikecount. And there we go. When you refresh both of them should be 0 0. One problem is that we still have no idea what is the users reaction to that.
In order to get that to work, we need to create a comment table expression. Let's create const viewerReactions and let's add a database with viewerReactions as database. Select commentId from commentReactions, commentId a type of comment reactions type. From comment reactions, where, and now we need to add the query to load by the current user id. But remember, this is a base procedure, meaning it's public.
So what we have to do is we have to extract our clerk user ID from the context. This might be null. So what we have to do is we have to optionally and potentially get the user. So let's define a potential user ID. Then let's find the user using await database select from users where in array, make sure you import in array from drizzle or RAM.
And now we are going to check. We are looking for a matching users clerk ID, but only if we have clerk user ID. So if we have it, that's gonna be our array. Otherwise, we are going to set it that way so that it will always return zero users. Because remember, If we just did where users, for example, users, if we just did this, clerk user ID and then optional equals users clerk ID, clerk user ID, whoops, Clerk user ID or undefined.
If we just did this, this looks like it will work, but remember, this will simply query all the users that exist. So that's not what we want. We want this. We only want our user to be possible to be loaded. And then what we're simply going to do, if user, user ID is equal to user ID.
There we go. And now we have the user ID. So now we can go ahead and do the same thing here. In array, comment reactions, user ID, and then user ID, otherwise an empty array. There we go.
Now we can include the viewer reactions inside of our promise here. So let's add with viewer reactions like this. And now what we have to do is we have to join those reactions. So the similarly as we did an inner join to find the relevant users, we will now use a left join because it's not necessary for us to find any reactions from the viewer. So let's add viewer reactions and let's query them further by the comment ID.
Viewer reactions, comment ID. There we go. And now we can finally add that to our select here. So above a like count let's add viewer reaction like this. And the viewer reaction is going to be viewerReactions.type.
So now finally, we are able to check whether we liked or disliked something. So let's go ahead inside of here and let's check if comment viewerReaction is equal to, So this is thumbs down and this like count. So we're checking if this is dislike. In that case, go ahead and add fill black like this. So I will copy this class name.
I will go to the thumbs up icon here. And now I'm just going to check if the like one is true. There we go. Nothing much can be done yet because we didn't implement the like and dislike procedures themselves. So now let's go ahead and focus on that.
In order to do that, we have to go inside of source, inside of our modules, and I believe we have to create comment reactions. But let's make this easier for us by copying the video reactions module. And let's create comment reactions here. Let's go inside of procedures and let's rename this to comment reactions router. Let's immediately add this to TRPC routers app so we don't forget.
I'm going to call this commentReactions and we are going to import commentReactionsRouter from the modulus commentReactionsServer procedures. Now let's go ahead and continue building here. The like method will work similarly but instead of having a video ID we will work on having a comment ID. This will be existing comment reaction like from comment reactions. Import that from database schema and you can already remove the video reactions.
Now let's go ahead and query by comment ID and use the comment ID from our input here. So now we are looking at an existing like from this user. If an existing like exists for this comment, we are going to create a deleted viewer reaction. So we are deleting again commentReactions and commentID. There we go.
And now we will have created commentReaction here, which means we are inserting into comment reactions and we are inserting comment ID with the type like in this case. And let's just replace this too with my apologies, comment reactions, user ID, and this will be comment ID. So the same thing, if a conflict exists, because in here we only check to toggle like to unlike, but that isn't necessarily a dislike. So a dislike might already exist. If it exists, we are going to reach a conflict because we are trying to conflict a primary key combination of a user ID and the comment ID.
When all we actually want to do is simply update this user ID comment ID to a like because if a conflict happens obviously this user has previously disliked this and that's why this unique combination of user ID and the comment ID exists. So now we have the created comment reaction and we can return it. Great! And now we have to do the same thing for the dislike here. Again, we are passing the comment ID here.
Let's change this to existing comment reaction dislike. Let's replace all of this with comment reactions. Let's replace this with comment id. Let's go ahead and properly check an if clause here. Change all of these instances to comment reactions.
Change this to comment id. Change the created video reaction to created comment reaction, change these three instances to comment reactions, and change the video ID in both of these places, the values and the target for the conflict to comment ID. And now just return the created comment reaction. So this is what I really like about our API design is that it's so consistent that we were able to just change the naming of things and everything else was pretty much the same, right? I really like when that happens when we code because I feel confident because everything that worked previously will most certainly work now.
So let's go ahead and set up the comment item. And what we can do now is we can add the likes and the dislikes. So let's add const like to btrpc commentReactions like useMutation. And let's go ahead and change this to dislike. There we go.
Now let's go ahead and find the like button and let's call like mutate and pass in the comment ID to be comment.id. Now let's do the same thing here. Except in here we are calling this like. And let's disable this if this like is pending. And let's disable the like one if like is pending.
And what we have to do now is we have to add our utils. So const utils. Actually, we already have the utils, so that's great. Let's go ahead and do the following inside of here. OnSuccess.
Post. Actually, we don't have to create any success message. What we can just do is call utils comments get many invalidate for this specific video ID, comments video ID. Comment, my apologies, comment.videoID. There we go.
Whoops. And on error here, we can check for the error in case an unlocked user tried to do this. Let's do toast error something went wrong and let's now do if error data code is equal to unauthorized let's just do a clerk open sign in. The reason I didn't do any success message here is because this can either be hey you just like the comment but it can also be you just removed the like from a comment. So let's just open an object here and paste that here.
And I think everything should work now. We don't need this. We don't need a comment schema. That was an accidental import. And I believe that that should be it.
If I click like here, let's check. There we go. One like. Let's try disliking. And I think I know what I did here.
Again, dislike. I forgot to define the dislike procedure. I think I did the exact same mistake in the video reactions. Let's try now. There we go.
Now I can update on conflict or I can completely remove the record. Let's confirm that in our database here. I'm going to refresh. I should have only one comment reaction, which is a type of like. Let's go ahead and check it's created at last three numbers, 334.
So if I change to dislike now, what I expect is that I still have the exact same asset here, but this time the type is dislike and 334 are the exact same numbers, meaning that this was the old record. But if I remove my dislike, what should happen now is that it doesn't exist anymore. And if I add a new like, it should now create a new record completely from scratch. Amazing, amazing job. So let's go ahead and mark as done.
Comment Reaction Schema, Comment Reactions UI, and combining the comment reactions with comments get many procedure. Great! And you can even go ahead and try signing out and let's go ahead and go back and let's refresh and if I click like there we go I get prompted to log in. Same thing on dislike. Amazing, amazing job.
Next on our list is the reply functionality.