Implement Dynamic Buttons with Next.js Client Components

Jon Meyers
InstructorJon Meyers
Share this video with your friends

Social Share Links

Send Tweet

In this lesson, we create a new component to display the likes from Supabase. This component will be interactive so it needs to be a Client Component.

Additionally, we extend our Supabase query for tweets, and join the columns from our likes table.

Lastly, we make this button dynamic by checking whether the user has previously liked this tweet. If they have not, we increment the likes by 1, otherwise, we decrement them by 1.

Code Snippets

Querying data across tables

const { data } = await supabase
  .from("tweets")
  .select("*, profiles(*), likes(*)");

Resources

Instructor: [0:00] Let's display the data from our likes table in our Next.js application. Let's start by creating a new component in the app directory called likes.tsx. Because we're going to be clicking buttons and things, we need this to be a client component. We can then export a default function for Likes. For now, we're going to return a button that says Likes.

[0:21] Then back over in page.tsx, let's render our Likes component which comes in from ./likes. Then over in the browser, we can see our Likes button underneath each tweet. Let's fetch the number of likes for each of our tweets.

[0:35] Back up where we're making a request to supabase for all of our tweets, we're selecting all columns from the tweets table. Then because we have a foreign key relationship with profiles, we can get all of those as well. Since we have a relationship with the likes table, we can also grab all of those columns.

[0:49] Where we're rendering our Likes component, we can pass this one a tweet prop which is set to the current tweet we're iterating over. Then in our Likes component, let's get that tweet, and then, let's console.log out our tweet so we can see what it looks like. Because this is a client component, we can see it in the browser's console.

[1:09] Our Likes are currently an empty array. We can use the length property to show how many likes each tweet has. Back over in our code, we can remove this console.log. Then before the word Likes, let's render out our tweet.likes.length. Now we can see that both of our tweets have likes.

[1:26] Let's wire up incrementing the likes by clicking the button. Since this is a client component, we can use an onClick handler and set this to a handleLikes function which we can declare above, so handleLikes is an async function. Firstly, we're going to need a supabase client which we can get by calling the createClientComponentClient function, which comes in from our auth-helpers package.

[1:49] Then we want to await a call to supabase. From the likes table, we want to insert a new like. We're going to need a user_id and a tweet_id. Since we have this tweet prop, we can get our tweet.id, and we can get our user_id from our supabase client. Let's say const data, and destructure our user from awaiting a call to supabase.auth.getUser. We can then get our user_id from user.id.

[2:19] Since user may be null, we want to wrap this call to supabase in an if statement that checks that we do have a user. Now, if we go back to our application and click one of our Likes, we see nothing happens, but if we refresh, the Likes for our tweet have incremented.

[2:33] Let's refresh that data automatically, which we can do with a router which we get back from the useRouter hook, which comes in from next/navigation. Then after we've inserted our new like, we can call router.refresh.

[2:47] Now, back over in the browser, we can increment the Likes for either of our tweets, and continue incrementing them, but hang on, a user should only be able to like a particular tweet once. Clicking the Like button again should decrement it rather than increment. Let's remove those likes from supabase and refresh our application. We're back to a clean slate of likes.

[3:07] Back over in our application, we need to detect whether this user has already liked this tweet, and then either increment the value or decrement it. Back up in page.tsx, when we get back our list of tweets, we also get every instance of a like. Rather than aliasing this data prop to tweets, let's create a new array for our tweets which can be equal to mapping over our data.

[3:29] Then for each of those tweets, we want to construct a new object with all of our tweet data, and then a new field for user_has_liked_tweet, which we can get by calling tweet.likes.find. For each of those likes, we want to find a like with a user_id field that matches our session.users.id, which is in scope from our redirection logic above.

[3:53] Since we're transforming our tweets array into a convenient shape, we can also set our likes property to be a tweet.likes.length. We only want to do this if we actually have data, and if we don't, we just want an empty array. We'll see TypeScript is not happy, so we'll fix that soon.

[4:09] For now, over in our Likes component, we can render out our tweet.likes, which is now a number, rather than needing to get the length of the array. Then to check whether we want to increment or decrement the number of likes, we can add another if statement to check. If the tweet.user_has_liked_tweet is true, then we want to dislike, otherwise, we want to insert a new Like.

[4:32] To dislike the tweet, we can await a call to supabase. From the likes table, we want to delete the row where there's a match on the user_id column and our users.id, and the tweet_id column matching our tweets.id. Now, if we go back to the browser and like one of our tweets, we can see it increments to 1, and if we click it again, it decrements.