Client-Side GraphQL with React, v2

Update Issue Mutation

Scott Moss

Scott Moss

Superfilter AI
Client-Side GraphQL with React, v2

Check out a free preview of the full Client-Side GraphQL with React, v2 course

The "Update Issue Mutation" Lesson is part of the full, Client-Side GraphQL with React, v2 course featured in this preview video. Here's what you'd learn in this lesson:

Scott explains how to implement a mutation to edit an issue in a GraphQL schema. He demonstrates how to create the mutation in the schema and then import and use it in the component where the editing functionality will be implemented. Scott also discusses how Urql, a caching library, can automatically update the cache when the mutation is performed, without the need for a network request.


Transcript from the "Update Issue Mutation" Lesson

>> Speaker 1: If you already know how to do mutations, which you should we have a lot of examples, then this one is pretty much the same thing, right? We just we need to figure out our mutation. We have one called edit issue, we're gonna use it, we're gonna create it, and then we're gonna go into the component in which it exists.

We're gonna put it in there. And we're gonna edit our issues, but we also want this change to be reflected whenever you click edit issues. So we'll have to see if we need to do something, or maybe it might be done for us for free. And if it is, I'll tell you why.

If not, we'll fix it we're gonna say EditIssueMutation, and we're gonna go and pass on our mutation. I'm not gonna copy from notion because they have tabs apparently. And instead I'm gonna go here to Apollo studio. I'm gonna click on my explorer and I'm going to use that sound.

We're gonna make the mutation here, I'm gonna say I want to edit the issue. I'm gonna say I want to, get back. I wanna get back the same fields that I got back from the issues query. Because if I edit this issue and when it comes back to me, I want it to fit exactly what the other issues in the list, without it having any missing field.

So it works seamlessly. So I'm just gonna ask for all those similar things. I know I have an ID, I know I have a name there, I know I asked for content, and I know I asked for status. I don't think I asked for anything else on the issues query.

I can go check. So I'll go to the issues query and I can see, yeah, content, id, name, status. So I'm gonna add those four things, content, id, name, status. And this one has a non-null input. Obviously, if you're gonna edit an issue, what is the idea of the issue you wanna edit and what the hell are you trying to edit, right?.

So that's what these are. So you can see here, I'm gonna say, yeah, I want the id. And you can pick, id is required, but then you have to pick one of these. Are you changing the content? Are you changing the name? Are you changing the issue status?

In this case, I would change an issue status. So I can put status here. This part is irrelevant because we're not really gonna use this unless you're using it in the Explorer. What I want is this part up here. So I'm gonna say, this is the EditIssueMutation, and I'm gonna copy it, okay?

Go back to our app, GQL folder, editIssueMutation.ts, Import GQL from urql like that. Export const. EdditIssueMutation. There we go. So now we have our edit issue mutation it's written, we declared our variables here. We pass it into the input here on the input field of the edit issue.

We got the right fields. Everything seems great. So let's head over to, I think where we wanna do this is, it's gonna be on the status component, so go look for the status component in app slash auto score components. And yeah, I think we wanna do this here.

So because it is a mutation, we know we need to import use mutation. So let's do that from urql, urql/next actually.
>> Speaker 1: There we go and then let's import the mutation itself. So EditIssueMutation like that. So what we wanna do here is we get this new status. We just wanna go ahead and call the function so I can say await or actually we need to describe the hook first.

So let's do that, so let's get the hook, useMutation. That's gonna take in the editMutation like that. This will give me an object here that I'll de-structure and then this is the actual function, so editMutation. So this will either give me an error, some data, or a fetching.

And then I can say I wanna wait. EditMutation, which takes in object with the input, and then that object has to have an id on it, whose id is gonna be issue id from the props, and then the actual status that we wanna change to. In this case, it's gonna be new status.

So I'm gonna say status, newstatus. So we'll try that. Let's make sure that this on action is hooked up. On action is hooked up here. Don't worry about the TypeScript error, it's fine. On action is just, you'll see there's a dropout menu when you click it, on action response to that.

So let's go try, we'll go here, we'll just refresh just for sanity reasons. So now if you click on one of these rings here, the status rings, you can see it's currently backlogged. I should be able to say in progress or done. So if I say in progress, it changed to in progress.

If I refresh it, it should still be in progress. You can see kind of jumped down. I could talk about that in a second. But I can change this one to done. It's done. Refresh it, probably dropped down to as well. So, on the previous mutation that we made for creating an issue, we had to explicitly refresh the query to get the new state to show up.

But for this mutation, we didn't it just showed it immediately. Why? [LAUGH] Why did it do that? Yeah, the reason it did that is because urqel was able to figure that out for us, and this is why I didn't want to dive too much into this, I feel like it's quite complicated.

But if we go back to the GQL provider, urql uses this thing called a cash exchange, which I'm just gonna show you the docs so we can talk about it for a little bit. Here we go. This right here does something called, where's it, it's normalized caching. So normalized caching, it's complicated but basically what it means is, you see if I can find a good like it's getting deep here.

Let me see if I can find a good example of that, the best way I can describe normalized caching is, ergos gonna treat every entity in our schema as like a collection. So for instance, if I do a issues query, it's going to take every issue and put it in its own array in the cache called issues.

Every issue that I get, no matter what query it comes from is gonna put it there, right? So it normalizes all the data that I get versus saving it as the way that it came, right? If it wasn't normalizing, it would just cash it the way that it got it back and that was it.

So you might have duplicate issues everywhere. If it normalizes the issues, it won't duplicate them. It won't save them the way that they came back instead of it'll go grab all the issues, and put them in this collection. And now whenever you start doing things on an issue, it'll automatically update for you if you send back the appropriate stuff because it can match them by their ids.

So that's what happened on this last one. The creating the update if you go back and look here, if we go back to see what comes back from the editIssue, so editIssue sends back an issue, so does createIssue but, the editIssue worked because, there was already an issue in the cache.

So it knew, same id, cool, I'm just gonna update it for you, right? Whereas the new issue didn't exist, so it didn't put it there. We would have had to put that there manually. It wasn't smart to know that, not by default. I think there's some like hints you can like give it to tell it like, yeah, it gets crazy.

There's like some hints you can do to like tell it, but otherwise you'd have to write to the cash yourself to do it. But on the edit one, it was like, yeah, this one already exists, cool, same id, great, merge. The one that came from the server overrides the one that's in the cache.

It has a new status, it reflects immediately that's why it happens so quick without a network request. So that's why I think writing to the cache off of mutation is probably the best approach because you wait until the server actually responds, and then you write to the cache, right?

And then, in the meantime, show, like a local loading animation. So if you, if you seen the, what is it called? The shimmer? The shimmer loading animation? I think most people call it a skeleton I think. So like this. Show something like that, right? That way you're not showing a whole loading spinner or loading the whole page.

You're just showing just a little loading icon for that one thing. So in our example, it would be just for that issue, you might see something like this until we can confirm back from the server that the mutation was accepted and then we can write to the cache with that new one.

Because the opposite is like doing an optimistic update, where you just skip the loading period and you just show it on the screen and pray that the server sends back the same thing. But if it doesn't it's quite jarring and confusing for the user to see something pop up immediately and then have it be taken away almost as fast.

So I recommend this. It's a good middle growl, but I didn't feel like adding that, so I didn't add it. But you get the point. I did say I was gonna talk about why these things jump around, right? So if I do this and I create this issue, it's here, but that when I do this and refresh, only when I refresh, it goes down.

That's actually just how I'm doing the database stuff, right? So on the database query it's a little weird. Okay, I'm gonna show you some service stuff. Don't worry about it. Don't get too crazy. So basically, I'm ordering here by these enums. So, I'm setting an order, but that only happens on the server side, which happens when I refresh the page.

But when when the cache gets updated locally and the status reflects the frontend doesn't know this, the frontend doesn't know this part. It's not doing sorting, so it doesn't know to this thing got changed to progress, I need to resort it according to this logic. It doesn't know that, that happens on the backend.

So the front end will have to implement similar sorting to the database sorting for that to also reflect when the cache gets updated. Otherwise, it won't happen until I refresh and it comes back from the database with this order buy right here. So as far as implementing sorting and stuff like that in the query, how does that accomplish them?

Yeah, that's a great point. So how can you accomplish this in the query? Well, there isn't any argument that you couldn't ask for. So you would have to ask yourself, what about the ordering? Do you want to be dynamic? And then you would add that into your schema, right?

So an example would be, let me see, I have- So you do it in the schema? You have to define it in the schema, right? So like here you can see for the issues query, I allow you to pass up an array of statuses and I will return any issue that belongs to you, that is one of the statuses, so that's kind of a filter.

There's nothing stopping you from also saying, orderBy, right, and then what do you want someone to pass in? Maybe they can pass in a string, and that is something that you can use dynamically in your SQL query to do some type of ordering. So it's whatever part of your ordering that you want to be dynamic, make that a input variable inside your schema and then now it'll show up.

Cuz if I save this right now, watch, I'm gonna save this. So now you can see this is on IssuesFilterInput, right? So I'm gonna save that. I'm going to go back to here. I don't think I need to refresh cuz it's polling. So it might already be good.

And then if I go to issues, what did I call it? IssueFilterInput. Okay, and it's not gonna find it there. So let's just go to query, IssueFilterInput. There it is, or to buy it's already there. So that's how you expose it. There's another side to this, you now then have to take that value and resolve it.

And we'll talk about that in the service side of course. But yeah, you expose it through your schema, and then you use it in your resolver. Yeah. And there was a comment in the chat. Does this sound right? GraphQL allows you to build a local graph of related data, it knows the relationship between data, so it knows the data structure, and it knows what to update?

Close. So it's not GraphQL that knows that, by default, GraphQL doesn't have anything to do with updating cash. It doesn't even do a cash. It knows nothing about it. GraphQL Graph QL exists purely for the network layer. Well, technically, you can use Graph QL for anything, not just for networking and Graph QL is just a way in which you can query your graph.

That's it. It's a mechanism like here's, a syntax to query and then here's some tools to resolve those queries. That's all Graph QL is, it doesn't know anything about what's on the other side of that resolver as far as like a cache, a database, another service, another graphical server.

So no, it doesn't know anything about that, but it does provide the metadata in which URQL can then use to figure out what to update. And that's what I was describing as like the normalized caching. So, it was smart enough to know that that id existed because ids are keys by default in urql you can change the keys if you want and it just merged it.

It just merged it. So, not entirely urql, urql definitely needs the metadata from GraphQL, but GraphQL doesn't, it doesn't know, it doesn't do it. It has nothing to do with caching or anything like that? Yes. If we want to extend what we learned here to actually work with the server does urql automatically keep its local cache in sync with the server?

Or is it a different set of operations to actually update the DB? Does urql automatically keep its local state in sync, no. So I think what they're describing is subscriptions. So subscription would be what they're describing, whereas that would be like a web socket. So if you wanted your local application, in this case, urql, to be in sync with some data on the backend, you would have to set up a subscription for that.

We're not gonna cover subscriptions. For a lot of reasons, one they're actually not that quite, they're not that complicated, but like we're using Next.js and doing like web sockets on Next.js is like, you can't really do it because it's serverless. So you have to use a third party service for that and something else you got to sign up for.

None of them are really free or good. So I didn't do it, but you would need to subscription for that, yeah, otherwise, I mean, I guess you could pull. There's nothing stopping you from pulling. Urql supports pulling so you could pull a query. And whenever new updates come, you can do that.

And for some UIs, you might do that. Like if you're on Twitter and you're on the Twitter feed and you're all the way at the bottom, Twitter shows you a bubble at the top of your feed, hey, you got 10 new tweets, click here, you click it, and it scrolls all the way to the top and it loads in those two new ones.

They're probably pulling that, right, there while you're on your feed. They're just going to their server every, I don't know, 30 seconds, maybe I don't know what they're doing. They're asking like, hey, are there anything new since this timestamp anything new since this timestamp, if there is they show that w bubble that you can click on.

So that's some way you could like keep it in sync. That definitely wouldn't be real time. You have a gap between whatever your polling window was. But other than that, you have to use subscriptions. Yeah.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now