Client-Side GraphQL in React Optimistic UI Updates
Transcript from the "Optimistic UI Updates" Lesson
>> Scott Moss: So if I add a delay to this lets say mid here and let's add a newPet.
>> Scott Moss: Jenkins, cool, we can just load in screen, pretty awesome, right? I'm not a fan of that, I don't like loading screens, I just don't like it. I mean, maybe you work really hard on that spinner, and you like it, and it's clean.
[00:00:24] You got it off CSS tricks or code pin and you're like, this is like the number one rated spinner. And I spent two days making it a react component and it's just so great. I centered it, it's so awesome, [LAUGH] I know how we get with that stuff.
[00:00:39] So I actually don't like it, I don't like spinners, I like more, I don't know, fast filling UIs, things that are more optimized. I'm in a boat of kind of what the Facebook's doing with the world, where they show you the skeleton of a component and they load it in gradually.
[00:00:57] I like things like that, that's called optimistic UI. So your UI is optimistic that the server is gonna respond back with exactly what they think it is. So they just went ahead and did it for you anyway, right? If you ever played a multiplayer game, how the architecture works, you have local state on the multiplayer game.
[00:01:16] So when you're playing a fighting game and you press a button, it's gonna happen on your machine first. And then it goes to the server, and then the server agrees with all the other clients and then everything works out. Everybody sees the same thing, but it updates on your machine first, right?
[00:01:31] When it doesn't update, that's when you get that lag, it looks different. We can get that for free with Apollo, so we can get a that optimistic update where we just update our local app before our server even comes back. And if there's a disagreement well see some lag, but there's not a disagreement, no one will ever notice.
[00:01:48] They won't even see it load in, it'll look like it happened instantly and I'm a big fan of that. Okay, yeah, Optimistic UI, so here's my definition have written down for. Basic ultimate UIs ,when your UI does not wait until after a mutation operation to update itself. Instead, it anticipates the response from the API and proceeds as if the API call was synchronized.
[00:02:11] The API response replaces the generated one, this gives the illusion of your app being really fast. That's basically what Optimistic UI is. So like I said, Apollo provides a simple hook that allows you to write to a local cache after a mutation. That's not the same thing that we just did, I know that sounds very simple to, wait, isn't that just update?
[00:02:33] Not quite, because update happens after a mutation comes back. So you can do optimistic update, after the mutations already done, you already waited. The hook that I'm talking about which is the optimistic response is going to happen. When, as soon as you execute a mutation, before it comes back.
[00:02:55] That's the whole point of optimistic response, so I'm gonna go to GitHub right click. I wanna simulate some network delay and we can do this in Apollo client pretty easily. Using,
>> Scott Moss: A new link. So I'm just gonna go grab this link that I have here, this delay link, which is pretty cool, bam, bam, bam, grab that, okay.
[00:03:26] So and grab that, too, all right.
>> Scott Moss: So I'm gonna go back to the client.
>> Scott Moss: And the thing about links with the clients is that you can compose them to do whatever you want. You can think of them as middleware, you have many links that do different things.
[00:03:47] You can have a link that Intercepts and grab a JWT from local storage. You could have a link that adds something to the header, you could have a link that logs. They're basically, if you've ever used Angular, they're like HTTP interceptors. If you've used any sophisticated HTTP client, like axios, they're interceptors.
[00:04:05] You can do things before and after a response is fired, so that's basically what links are. So we're gonna add a link that just delay some network delay, so we can actually see the benefit of having an Optimistic UI. Right now, everything's on local host, so it's so fast, you won't even notice it, but we wanna do that.
[00:04:21] So what I'm gonna do here is I'm going to import Apollo, did I not copy that, where did it go? There you go I wanna import Apollo link from Apollo link like that?
>> Scott Moss: And then I'm going to make, I'm gonna change this link down here to HTTO link.
[00:04:47] Move it above that one, I’m gonna make a new link, I’m gonna call it Delay. And basically, what it’s got to do, it’s gonna use set context which is gonna come from imports. I wanna say set context from apollo-link-context, like this. And this will take this takes, it takes a callback function that takes a request and you can return whenever you want.
[00:05:17] I'm just gonna return a promise that times out after 800 milliseconds to create some delay here.
>> Scott Moss: And then I wanna make a new link composed of those two links. So the delay link, and then the HTTP link and you can do that using the ApolloLink.from method, which you can get from ApolloLink.
>> Scott Moss: And you put them in order that you want them to run. So I want the delay to run first, then I want the HTTP link, and then I can keep this link down here. So basically, from just generates one link based off a whole bunch of links and whatever order you send them.
[00:05:53] Like I said, it's literally middleware, whatever order you want, that's the order they're gonna run.
[00:06:31] The update is actually, I'm sorry, the optimistic UI stuff is actually pretty simple. Basically, there's two ways you can use it where they're both the same way. But the two places you can use it, so you can add, basically, the property you're going to use is called optimistic response just like this.
[00:06:50] It's gonna be an object, it's not gonna be a function. So, yeah, optimistic response as a key on the object that you pass the use mutation. But you can also add optimistic response to the key inside the mutation function that you call. The difference is basically, it really depends on what you need access to, right?
[00:07:14] So if I wrote a call to optimisticResponse object up here on the useMutation, then this is gonna happen for every single mutation. Every single time createPet is invoked, this optimisticResponse is gonna happen, so it's global, right? If I were just to call it down on just this instance, it's only gonna happen when I invoke it just this one time, if I invoke it again, it won't happen again.
[00:07:39] Also, if you need access to any input variables, then you wouldn't want to place it on the use mutation. Because you don't have access to the variables that this mutation is using, I don't know what any of the variables are, I don't have them. The variables don't come in until you actually call the function, the variables are here, right here.
[00:07:58] So if we need to access the variables for the optimisticResponse, then you're gonna need to put them on the createPet. So now, you know where you can use them. Basically, the way OptimisticResponse is, the whole purpose of it is that you're trying to return an object that's going to look exactly like the objects that you think you're gonna get back from your server.
[00:08:18] And that should be pretty easy, because we're using GraphQL. And we know exactly what we're getting back from the server. So we should be able to determine what the objects gonna look like. So in my example, if I was creating a pet, what am I gonna get back from the server?
[00:08:33] Well, I've just got to look at a mutation, I'm getting back an ID, I know that's a stream. If I didn't know that was a stream, I can go look at the schema inside of Apollo Tools. I know that's a stream or an ID type, same thing. I'm getting back a name which I know is a stream.
[00:08:44] I'm getting back a type which I know is an enum. I can even go look at those enums in the schema. And I'm getting back an image which is also a stream, so if I return an object that looks like that, I should be good to go. That’s basically, what you have to return here, plus some extra things.
[00:09:03] For instance, you don’t see this, but Apollo and most graphical frameworks automatically add an underscore typename to all your queries. So every single query that you have, this gets added. You just don't see it in the tools like playground, they just get rid of it. But if you look at the response on the data that's coming back from the network, you'll see this.
[00:09:24] And the typename is gonna be the name of the type that you're trying to retrieve. So in this case, the name of this type would probably be Pet with a capital P, cuz that's what our schema defines this as. So you would have to add those type names here as well for every single type.
[00:09:38] And not just for the types that you're getting, but also for the higher level types, like for instance, mutation or query. You got to add those type names, too, you got to be very explicit about exactly what you're gonna get back. And like I said, these are the things we know we're getting back.
[00:09:55] And now I'm telling you about the things that definitely come back, you just don't see. And this is the time where you have to be explicit about those. So basically, show you what I'm talking about. Basically, I said you return an object and in this case, the type name is probably always gonna be mutation.
[00:10:11] Cuz you're only running optimistic response when you mutate something. So the overall operation, it's type name is mutation, so a mutation is a type. It's just a very specific type that graph cares about, but it is a type, just like Pet is a type. So we're gonna say type name, mutation.
[00:10:26] And then from there, you add a key with the exact name of the mutation that you're in. So if your mutation was called at pet that's what this will be called, it will be called at pet. You're running at pet, and then you will start putting all the fields that you expect to come back.
[00:10:40] And then the only difference is you have to get those fields values, you need to give them values that you think your server is gonna give you back. Some values you can't compute, for instance, you can't compute an ID, your database creates ID. How would you know that?
[00:10:52] That's fine, you don't have to put the exact ID. This whole thing is gonna get replaced when it comes back from the server anyway. But you need to put a value that satisfies the ID requirement. So in our example an ID is an ID type, which is a string.
[00:11:04] So you could literally put any string you want in here. Or if you actually have the ID, because maybe this mutation is an update. So you have access to the ID through a variable, you can actually put the real ID there. In our example, we're doing a creation, so we don't have access to an actual ID, so we got to make up one, so you'll make up an ID.
[00:11:22] You also have to put the type name for the type that the update comment, in this case, mutation returns. In your example, it'll probably be Pet with a capital P. That'll be the type name, and then you need to put all the other fields that you expect that mutation to come back with.
[00:11:36] So things like an image which anticipates it being your stream, you can put a placeholder there. I think there's also a type for a pet and it has to be enum. So it has to be either a DOG or CAT in all caps. You have to put a name, you can make it whatever you want, or you can use the name that's coming in as a variable.
[00:11:51] Cuz you have access to that, same thing with the type, you have access to that. And then that's all you gotta do is you need to return an object that looks exactly like the response you expect to get back from the server. You know you got it right, because one you won't see any warnings in the console, cracky will probably give you a warning.
[00:12:11] If you try if you miss a field or something, I couldn't do this, because you don't have an ID field, couldn't do this, because this image isn't there. It'll give you a warning, it won't break, cuz it gives you a warning, or at least Apollo it not GraphQL.
[00:12:23] But you know you got it right, because you won't see a delay, so that delay that I added. Let's take a look at it. So if I add a newPet, that 802nd delay, you won't see that you won't even see a loading screen at all. It just won't be there, it'll just be good to go.
[00:12:43] But in order to enable that, you need to opt out of having a loading screen in the first place. So let me get rid of this, get rid of that. You need to tell our Boolean here, cuz right now, at least for me, I have if the query is loading or if that newPet mutation is loading, then show the loader.
[00:13:05] Now, I never want to show the loader for the newPet mutation, just don't even show it, cuz I'm opting in. For optimistic UI, I never wanna show a loader. If you still have that, even with optimistic UI, it's still gonna show a loader. Because the loading is still gonna toggle, the true or false, even though you optimistically already updated it.
[00:13:21] You don't wanna do optimistic update and have a loading screen that kind of defeats the purpose. So you gotta turn one of them on and other one off. So I'm opting out of showing a loading screen for my newPet mutation and I'm opting in for optimistic response.