Fullstack TypeScript (feat. GraphQL & Node.js) Adding Favorites Feature
Transcript from the "Adding Favorites Feature" Lesson
>> The next feature we're going to add to our Twitter app is the concept of favorites. And this is going to involve two mutations because we're gonna treat favorites as its own resource. It's sort of a many to many relationship where you can think of a favorite as the join table.
[00:00:17] Something that refers to both a tweet and a user where a user can like many tweets and a tweet can be liked by many users. It's effectively all this is going to be. We're going to have to create these favorite entities and we'll have to destroy them when I use unlikes.
[00:00:36] So we'll get to practice building two more simple mutations. The first thing we are going to have to do is add a little bit of code to our GraphQL Schema. One more type for this new favorite entity. So let's copy that, open up our Schema. And paste it right at the bottom.
[00:01:00] So note that favorite has a relationship with tweet and user and those are non nullable meaning we're going to have to populate those anytime we're returning a favorite of sorts. Next, we're going to have to connect this concept of a favorite to user. So here we go. Let's just put it right here at the bottom.
[00:01:31] So a user has a field that's an array of favorites. So can somebody help me think about whether it's possible to have nodes in this array? Or whether it's possible for me to get no back instead of an array? Is the collection nullable? And second question, are members of the collection nullable based on the Schema?
>> Collections novel.
>> And the second part,
>> no, no,
>> The collection is nullable. So favorites could be be null instead of an array. But if you get an array, you can rely on the fact that everything within the array is of type favorite. That's what this exclamation mark means.
[00:02:25] Great, you may also note that we have like favorite refers to a tweet. But in this case tweet does not refer to favorites. This is actually fine. This means that potentially when we're pulling a tweet, we don't want someone to find the list of users who have favorited it.
[00:02:53] Maybe we only want to expose that information to the stats object where you get the count of the favorites. But maybe in the future a product manager comes along and says, we want to show the little face pile of like, have all of your friends who have liked this thing.
[00:03:08] Then we would need to establish that relationship, but there is no requirement that you need that symmetry in order to define the relationship. This is about your API's contract with the outside world not necessarily a fully fleshed out diagram of how your entities in your database relate to each other.
[00:03:29] So important to remember that because in some cases you don't wanna expose more information than you need to All right, last step that that relates to the GraphQL changes, we're going to go to our app react component, and in this get current user query, we're going to retrieve the Favorites array.
[00:03:57] So here we go, it's within the current user. And all we want to get back is so we want the tweet and then within the tweet we want the ID. So this is going to give us an array back and each item in the array, is going to be an object containing a property called ID, whose value is a string.
[00:04:28] Object with an ID in an array. All right time to run Yarn CodeGen because we've changed stuff on our client and our server Yarn. CodeGen and then on server. Yarn CodeGen and everything looks good. So, what this means is I should be able to I just test this real quick, make sure everything's wired up.
[00:05:03] I turned it from a value to a key just by adding more destruction to the right of it, right? So here we go, that's what I wanted to see. It is objects sorry it's more complicated than I even described previously. These objects have a property called tweet and within that like the value of tweet is an object containing an ID.
[00:05:26] So I did this deliberately because this is a common pitfall that people experience with React. As soon as I show you what this data looks like, you're likely to ask me question and that question is, how do I flatten this down? It seems like I just want this array of IDs and I'm having to effectively map over this and drill in and if anything's nullable as you drill in, you have to deal with that.
[00:05:55] GraphQL does not really solve that problem, it's gonna give you data in a format that's aligned with the schema and align with the types. So I'll show you what I mean. I'll show you what this data looks like in its current state. Actually, we don't have our resolver I've wait to show you that.
[00:06:13] But that is a problem that Graph QL doesn't solve, and it's kind of a common complaint that things get nested in a big way. All right, let's keep going here. So we made this change, great. We ran Yarn CodeGen on both client and server, great. Time to add a new transform to our transforms TS file on the back end.
[00:06:43] So let's go to transforms TS and paste this in and pull these imports up to the top. This is a favorite transform, it doesn't do much but it's good to have there. Organized imports for that yep it combined them for me which is great. So that's all it is.
[00:07:05] It's everything about a favorite, except for that user object and the tweet object. So it's just sort of passing through everything else. Next, let's go and import that transform into our user resolver. So it's in the resolvers folder, User.TS And how are we going to use that? It's user.favorites.
[00:07:45] Do the old restart everything you, okay? What does this code we just pasted do? You know what, I'm gonna disable this lint rule real fast, so that I can stop having to kick ESLint so many times It should be reading from my engines property, that my version of node is not 8 I think I have to put that in here overrides.
[00:08:18] That's for the server stuff. We're just gonna turn this all the way off. Because something's weird, okay. Sorry about that little squashing of an annoyance. So what does the Favorites resolver do? Well, it takes in a user as an argument because the only way that this is getting called is if we've asked for user.favorites.
[00:08:44] In fact, it's safe because we're in user resolvers, that top level user thing will always be called, like current user, right, or tweet.author.user but somewhere you're passing through a user to get to this. So we're getting the user favourites and this returns an array of DB favourite. Then we're mapping over them.
[00:09:08] We're transforming them. We're placing the user into the record. Remember, this is already the GraphQL representation is the already ready to go user that's ready to serve up the parent node in the graph. So we can place it right in there, and then the tweet, we can get the tweet by ID.
[00:09:29] And then we can do a tweet transform here. So, we have our user and our tweet. And we should be able to make this work in our GraphQL studio now. Let's give it a shot. All right, we have one favorite and that ID. I have to make it easy to see that we're getting the right things back.
[00:10:01] IDs always begin with a hint of what kind of entity it is. Let's add a couple other things like, tweet body. So there's like you can see a list of things. What are the tweets that I favorited? But let's actually go back and let's look at what we're really trying to gather here This is what we're gonna consume on the client.
[00:10:29] So, it's gonna be an array of objects that have a tweet property, whose object has an ID property, that has the string we're interested in. So, this is sort of that deep nesting that can get exhausting at times. And there's really not much you can do about it.
[00:10:50] This is what we deal with when we work with Graph QL. It doesn't solve all the problems. This is it's workable you're an array.map away from getting what you want so it's not not too bad. But it does mean that there's some complexity and what you get back.
[00:11:06] All the more reason that having those types is important so that you can feel confident as you drill in what's there, what might be there and so you can solve this yourself more comfortably. So let's jump on to the UI side and wire this up. We're going to be working in the app.tsx component.
[00:11:25] And we're now, finally, going to get rid of this current user piece of texture data. So we're going to pass this directly into the left side bar. And this is the only change that's not trivial. So, we're mostly gonna just replace this current user and snake case all caps with the value that's really coming from the back end.
[00:11:45] This is a case where we merge two things together. And this is where we can get away from that merge entirely because it's no longer necessary. Okay so app.tsx find all occurrences of current user actually we can start by deleting this fixture data boom and find all occurrences, replace with current user, and then we can get rid of this, this is nonsense now like creating a clone of an object and mixing it into itself doesn't really make much sense, all right.
[00:12:24] Finally we're just gonna have to reorder some lines, why? Because we need this get current user query to kind of happen first before any of our destructuring happens. So I'm just going to kind of move the hook stuff way up to the top of the function and everything should work nicely Let's check out the UI.
[00:12:51] So what we expect to see now that favorites are coming in, is I believe we'll see numbers next to the favorite count for every tweet There's one favorite here, there's four, this shows that I as a user have already liked this one right? Because that's part of getting that array of Favorites with the tweet IDs for the current user.
[00:13:19] That's what I'm using as we go through and render the list of tweets. Is this tweet already favorited? Here's an array of what the current user has already favorited. Is your tweet ID in this list? If it is, you're favorited, right? So, great, what we've just done is now this is live data, this is coming in and it's live data.
[00:13:42] What we have to do next is wire up that create favorite and delete favorite mutations.