Fullstack TypeScript (feat. GraphQL & Node.js) Favorites UI Interactivity
Transcript from the "Favorites UI Interactivity" Lesson
>> The next thing we're going to add to our Twitter app is, Favorites interactivity, by which I mean, the ability to click that like button, to like, or unlike, a tweet. So, we're going to introduce a new concept here called an input type. And you can think of this as kind of a reusable object type that you might refer to in variables for your mutations or for your queries.
[00:00:29] So, we'll add this to our schema. And let's put it up here right below the top level, query and mutation route types. And then we're gonna add our two new mutations, CreateFavorite and deleteFavorite. And we'll look at those in a second. Deleting our plus signs from the diff.
[00:01:03] So, looking closely at these new mutations, note that we have an argument called favorite, and we're referring to favorite input in both of these cases. So you think of it just sort of like a reusable object type that you can refer to by name. You're never querying this thing and getting a result back that looks like this, it's really for variables only.
[00:01:32] So next, we're going to go to the tweet component. I think this is the first time we've touched this component. So, let's add all these imports to tweet.tsx. So add them right at the bottom. And there's gonna be a mutation for sure, two of them. So, we're going to say CreateFavorite and deleteFavorite in each case we're passing in a favorite input variable, which is gonna contain, of course, the tweetid and the userid.
[00:02:09] And what we're gonna get back is just the id of the like, right? The id of the Favorite entity we just created. Given that there's not a whole lot of interesting stuff that we can do with the information we get back, this will at least tell us something persisted.
[00:02:27] So we're gonna paste this right above the component. And as usual codegen, on server, on the client, To get all of our types updated. So, we're gonna take on the backend work first. To begin, we're going to need to import the favoriteTransform into our mutation resolvers. So we'll go to mutation.ts and add this import.
[00:03:05] In fact if I use organized imports, What happened there? [LAUGH] I hadn't used it yet. So it removed it, it's like, I kinda told it to. I can do this one manually. We have an existing import up there and it bugs me to have multiple things coming from one module if I can avoid it.
[00:03:29] So we're gonna add two new async resolvers that have to do with creating and deleting a Favorite. This is all going to go into mutation.ts. These are top level things that are happening. And looks like things are type checking as we hoped. So, these are both very similar, so I'm just gonna go over one and then point out the one difference between these two.
[00:04:08] So, we're getting favorite as an argument is of type Favoriteinput, which as you would hope is gonna have, A tweetid and a userid just as we defined in our graph QL schema. So there's our generated types, working well for us. We're going to create a favorite, which returns a promise.
[00:04:28] When it resolves we get a db favorite. This is that flat entity that gets up serialized as a JSON object in our database. Then we're going to return the transformed favorite object, right? This here transform, that is my own word that I'm using to describe converting from a database representation to a graph QL representation.
[00:04:51] And we're adding these embedded records here. Getting a user by id, transforming the tweet, right? We could probably do some more stuff here in terms of caching and things like that. However, remember, this kind of operation we gotta think about this end end, like when is this happening.
[00:05:11] This is proportional to a user clicking in the UI. So I don't expect this to happen like n plus one times, this is a top level thing. It is not something we're iterating over a list of things, and we're fetching the tweet and fetching the userid, so this is probably okay.
[00:05:29] We don't really need to think about optimizing this too much. If I were doing this for real, I would almost surely want to check to make sure that the user we get back is valid and that we're not creating tweets for users who do not exist in the system, or likes for tweets that don't exist in the system.
[00:05:53] So there's our CreateFavorite, and look at that. This is the only thing that's different, deleteFavorite, that's it. They're both async, they both return the entity you're operating on, either the thing that was just created or the thing you just destroyed. Everything else is the same. All right, time to gonna go to the front end.
[00:06:17] So we're gonna go to tweet.tsx. We'll start using the id so we can remove this underscore in front of it and simplify things. And then we're going to create again two things that look very similar, I'm gonna paste them in and we will talk about them. So, right before we return, Here, I'm gonna paste this stuff from the website.
[00:06:49] So, and we gotta change that id thing up here cuz we're using this now. So, what's going on? We are using newly created mutations that correspond to these new operations we created up here, right? So these are the things we're importing from our generated code, down here, we're just using them in their react hooks.
[00:07:19] What are we getting back? We're getting back the function we use to actually perform the mutation. We're getting back an err, and I'm having to sort of alias that property because we have two of them now. And down here I wanna be able to disambiguate between err creating the Favorite versus err deleting the Favorite, but everything else you should have already seen.
[00:07:40] Now, this is where I'm doing something, I'm advising you don't think about things quite this way when you're doing your own code. We're being very heavy handed in terms of prefetching queries. This will get the UI working, but I don't think we need to fetch all this information and like boil the ocean just because we'd like to tweet.
[00:08:02] I'm optimizing for different things here. Like teaching you a set of concepts in a particular order, not making you jump around to a bunch of different files. So, if you're feeling like this is a bit ridiculous, and that you wouldn't do this in a non training project, I hear you.
[00:08:21] But this will make it work, when we fetch a lot of things. All right, now, we're going to replace the contents of the handleFavoriteClick function that's in the same component with this. And similar to when we created a tweet and we had that synchronous placeholder method that was just there already wired up to the react stuff, now we're dealing with something that's async, and so we got to catch the err.
[00:08:49] This is always a good practice when you have a dangling promise. This means that you at least get some feedback if things blow up. And it's just generally good thing to do. Good for your future selves to leave so you have clues to track down when things go wrong.
[00:09:06] So, up here is this handleFavoriteClick method. And we're gonna replace this thing that used to console log based on is favorited. And now, we still switch based on this. And it's create or delete based on that variable. We still see a red thing, get current user from app.
[00:09:40] Save to bump the ts server. Are you happy now? It is not. Why is this not happy? Did I rename that foolishly? I did, I replaced all current, sorry that's my bad. I did an aggressive ctrl f and replace. No harm done. I wanna check one more place to make sure I didn't really screw things up our timeline.
[00:10:23] Sorry, I'm just trying to think where else might have used that. Nope, what's being used here, that didn't get changed, I think we're good. Okay, so we should now check to see whether we can like and unlike tweets. So let's refresh, click some hearts. Hey, look, the counts are going up.
[00:10:45] And unlike, count goes down to 0, to 1, to 5, to 4. This is all using set state behind the scenes. Again, a little, there's a lot of data fetching that's happening in the background. But on the one hand, it's a little overkill for such a simple interaction, and we probably could wrap this in a container component and validate something on a little smaller scope, like invalidate the data on a smaller scope.
[00:11:13] On the other hand, what we're doing here is gonna be proportional to a user interaction that's not gonna happen like 1000 times a minute, so no one's gonna be melting down a data center due to this. Great, our Favorites are interactive.