Use React Suspense to Simplify Your Async UI - Paid Workshop 2019-11-18 Part 3

Kent C. Dodds
InstructorKent C. Dodds
Share this video with your friends

Social Share Links

Send Tweet
Published 4 years ago
Updated 3 years ago

Use React Suspense to Simplify Your Async UI with Kent C. Dodds

Instructor: [0:00] All right folks. I hope you had a nice lunch break, or dinner break, or whatever time of the day it is for you break. We are going to move on to our next thing, which is number four. This is the suspense for image loading. Let's take a look at the problem we're going to solve, and then we'll talk about approaches for solving it.

[0:28] If we go to exercise number four, and we clear all this out, then I'm going to go to slow three...actually, here first. Let me just make sure I have nothing cached. Clear all this out, and then we'll go to a slow 3G connection. I'll go to Pikachu, and then right there, that state, right there, boom.

[0:49] Do you notice that really small state where Pikachu's image wasn't showing on the screen? Let's try this again. It should be recording frames for me when I do this experience, but it doesn't seem to be doing that.

[1:12] Here we go. I'll go slow. We go Pikachu. There's nothing there. That image just didn't load. That's the experience we want to fix, and not just for that initial load. Watch this. When I click on Charizard, we're going to see Charizard's data gets loaded into here, rendered into here, but the image doesn't update at the same time as the data.

[1:36] We click Charizard. Charizard is here, but Pikachu is still hanging out until that image loads. This is just because the way that the browser manages images is you hand a source to the image. Then the browser actually manages the loading state and the error state and the loaded, the successful state by itself.

[2:00] There's no real API for you to interact with all of that and show your own fallback image while it's loading. Basically, the fallback for an image is to just show what it showed before or to show nothing if it didn't show anything before.

[2:18] We can solve this by saying all of our images are going to be this exact dimension so that it's at least not jumping around, but then you're still stuck with the situation where you have an old image until that new one gets loaded, which is not cool.

[2:33] That's the problem we're going to solve. Loading images is just a matter of creating an image tag, setting its source, and then you can be notified when that image has all loaded and saved into the browser cache by the unload event.

[2:53] If it's saved into the browser cache, that means that as soon as you create an image tag or render an image tag with that source it should load instantaneously, so long as the user hasn't checked this little disable cache button, which they shouldn't do that.

[3:08] That's what we're going to take advantage of is this capability for preloading images. We're going to do that by creating a custom image component. This one place where you're going to be making a promise in render.

[3:25] You're going to fire off your promise during the render phase of your image component, so you have to do so really carefully. Here, we have a promise cache. You can look at this as an example of how to build your image component, but yeah. That do async thing will basically be this preload image thing to preload that image.

[3:46] Yeah, that's pretty much all I'm going to share with you about this. There are a couple of approaches to loading this image. The exercise is this first approach, and then the extra credit is the second approach.

[3:58] Then we have another extra credit to implement render as you fetch for this particular exercise which should show an even better waterfall kind of thing. We'll take a look at that when we all come back together. Before we split up, does anybody have any questions?

[4:22] OK, cool. It looks like several of you left to the Zoom meeting and then came back later, or something. You might end up in a different room. I'll try to put you in the room you were in before, but you might end up in a different room. Just introduce yourself. Say, "Hi," and then we'll be good.

[4:36] OK, here we go. All right. Coming back. Oops, here we are. Let's see what other things are hilarious. "If you ever throw out an I love Vue and you don't get I love Vue in return, follow it up with React is great too. But there's something I just love about Vue." That's funny. That's good.

[5:06] Let's get rid of Jira. It does not spark joy. Hey, my shortcut keys have stopped working, for some reason. That's weird. That's creepy. All right, great. How do we avoid this problem where images take forever to load? Let's fix that one.

[5:34] On this one, it's best to uncheck the disable the cache checkbox, because we're going to rely on the browser cache. I'm going to make sure that I keep that uncheck during this exercise. I'm also going to restore original fetch, so we get a fetch.

[5:51] We're fetching from the real database, but I'm just going to have that real back end. Then I'm going to create my cache object here. We'll have our image resource cache right here, and I'll have an image component that's going to take my source and the rest of my props.

[6:10] It's just going to return an image that passes the source, source, and spreads the props. To make [inaudible] happy, what I'm going to do is I'll take alt here, alt. It's annoying, but that's what we're going to do. Now, I can switch this to my image wrapper, and we were basically...

[6:33] That was just a refactor. There's nothing special going on here at the moment. We want to say, "Hey, if that source is not in my cache, then I have no guarantee of knowing whether the browser's ready to display this image."

[6:53] I'm going to put a resource for that source into my cache so that I can get that instant loading experience. What this is going to do is, by suspending before the image is ready to display, we can make it so React doesn't even try to show the transition, try to get us to render this stuff, until the image is actually ready to load.

[7:21] I'm going to come in here and grab this preload image thing here. Actually, I'm just going to come down here and borrow some of this here as well. I'm going to use that image resource as my cache. The value here is going to be our source, and then our async thing is this preload image.

[7:43] We're going to say, with the image component, the first thing we're going to check is do we have a resource in the cache, in this object? The key for it is the source. We're going to just assume that, if the source remains unchanged, or if that source URL remains unchanged, then we should have the exact same image in the browser cache and so it should be able to load instantly.

[8:10] Then, if there is no resource in there, then we're going to create one with this preload image, except we need to actually do a create resource, so we can read on this. We're going to do create resource with preload image.

[8:26] Then that is going to be our resource, which we will stick into the image resource cache. That's the important key right here, so that we don't create a new resource for every single one of these images, but we have it cached based off of the source.

[8:44] Then from there, we're going to come down here and say resource.read. This preload image is going to return a promise that resolves to the source that it's given with that on load resolve source. When we call resource.read, let's take a look at that create again.

[9:07] It's in our utils. Here's our async function. When that's finished, we're going to have a status of success. Our result is going to be r, and then we call read on that, and that returns the result. If we're pending, then we're going to throw that promise, and the component suspends.

[9:25] When React gets to this point, on that first render, it's going to say, "Oh, I haven't finished loading this image into the browser cache yet, so I'm going to suspend, and I'm going to prevent React from trying to trying to show this image. It's not ready. It's not in the browser cache yet."

[9:43] Then once that on load event fires, when the image is now in the browser cache, then we're going to resolve the promise, and React will say, "Oh, OK. Let me try and re-render everything from the suspense component on down and see if we hit any more suspending things."

[9:58] When that happens, then we'll come back through here. We'll say, "Do I have a resource at this source?" Yes, I do, because I created it on the last render. We'll come in and say resource.read. That will dive into this read function here. We're no longer pending. We're not in error. We are success.

[10:17] We're going to return the result, which was our source. We put the source into the image. Because that image source has been added to the browser cache, it's going to display instantly. We'll just look at what this does. Then you can ask any questions that you have about this.

[10:42] Let's clear all this out. Let's say, "Pikachu." That shows up instantly. There's no momentary "What's going on here?" If we go slow, then it'll show Charizard. It just waits until Charizard is all loaded before it shows the rest of the content. We can do the same thing for Mew. That takes a second because we're on slow 3G.

[11:10] Now here's the key point here. At this point, those images are now in the browser cache. If I go back to Charizard, then it actually will load quicker because we're still making that database request.

[11:21] When we try to render Charizard, Charizard is there already in the browser cache. We show it more quickly. That's how all this is working. Here's the code for that. What questions do you have about what we've done here? Let me just clean this up.

Audience Member: [11:43] Where would you transition around this?

Instructor: [11:48] Sorry. Joe, were you trying to ask something?

Audience Member: [11:51] I was raising my hand. I was going to say, "Can you just remind again why we're doing this?"

Instructor: [11:56] Yeah. We'll save all this. I'm going to just copy in. Then we'll undo all that stuff. Actually, here. We don't have to do that. We'll switch this to a regular image. We'll come back here, go online. We'll clear out everything. Everything is out of the cache.

[12:13] Then we'll go to the slow 3G. We go to get Pikachu. It shows us that fallback image right away. It loads up the data, but that image isn't ready. It's not in the browser cache yet, so we don't see anything. If we go to Charizard, we get that Charizard data right away, but it takes a second to switch from Pikachu to Charizard.

[12:34] That's what this solves. If we go back to our image, our capital image, our own component here, let's get this all loaded up again. Get the cache all cleared out just like your users would be.

[12:48] Then we go to Pikachu. We're loading. We actually could do something for that fallback image. Pikachu loads up as soon as it's ready. Charizard. We don't get any of the data or anything else until the Charizard image is loaded as well. That's the whole why behind this.

Audience Member: [13:13] Thank you.

Instructor: [13:14] Any other questions?

Audience Member: [13:17] Yeah. What I was wondering when I heard that last question is does it seem like we're reinventing what the cache does by itself? Maybe could you talk about why we're implementing this strategy and what advantages it would have?

Instructor: [13:35] Yeah. We aren't really reimplementing the cache. Basically, we're warming the cache, is what we're doing. The browser already has the cache all implemented. We have our own cache here just because of implementation details of how React is going to render our component.

[13:56] We don't want to create a resource for the same thing over and over again. Let's say you render two of the exact same image. React renders this one, then it renders this one. In both cases, it's going to create a brand-new resource for each one of those individual ones.

[14:16] By creating this resource cache, this is not an image. This doesn't store the bytes for the image. This is just a cache of resources which you can use to call read. Those resources are throwing the exact same promise.

[14:30] That's what the benefit of this cache in memory is, is to store those objects so that you can throw the same promise every single time this is going to be called, regardless of how many of those images you have on the page.

[14:45] We're not reimplementing the browser cache. We're just warming it up so that we don't display anything until the images are in the browser cache, so that it just loads up instantly. Actually, you've probably all seen this before. I am writing a novel here. Let's do a quick clear of my browser cache.

[15:12] Normally, older apps like this, when I want to add some time that I've recorded in my writing, then one of the things I can do is I can click on this "How did it go? Happy, sad," or whatever. They're using images to do this. They're not preloading the cache. What I see when I hover over this is there's a little blink. If I slow down my network, you'll be able to see it a little bit more.

[15:37] The image goes away because now we're going to load the image. If they were to prime the cache like we're doing, then it would switch directly from the image that we're seeing to the highlighted image. Now that the cache has been loaded though, I can hover over all these, and it works as you would expect. It doesn't go away.

[15:56] That's the problem that we're solving by using Suspense. We're saying, "Let's preload the cache before we render this. If we don't, then the image isn't going to be ready to be displayed to the user. We wind up with a janky experience."

Audience Member: [16:10] Got you. It's safe to say that just by calling document create image before we have to render the image element that uses that source, we're achieving this priming, cache-warming...

[16:20] [crosstalk]

Instructor: [16:21] Exactly. In fact, if we look at the utils here, this is where we have our Pokémon info fallback. This is using our fallback Pokémon image. What I do at the very top up here is I preload that image. I figure we're probably going to need to display this, so I'll just go ahead and get that into the cache right away.

[16:47] That would be a good way to solve the problem on their website. Once they open this up, they're probably going to want to hover over these. We'll just preload all of those. That would solve their problem.

[17:02] Ours is a little bit more complicated because people can type in whatever they want into here. We don't want to preload all of those images. We'll just preload them at the time we go to get the image for that particular Pokémon.

[17:16] That way we don't wind up with when we switch from one Pokémon to another it doesn't actually switch until that Pokémon's data is all loaded. That's the goal. That's what we're trying to accomplish here.

[17:37] [crosstalk]

Instructor: [17:37] There are some additional things that we can do to optimize this. Yeah, what other questions do you have about this? Sorry. I think I talked over somebody.

[17:48] [crosstalk]

Audience Member: [17:48] I just want to ask you. I saw on Twitter that Sunil is working on something related to image loading, right?

Instructor: [17:55] Yes. Sunil Pi, who's on the React core team, is working on maybe an image component or some sort of primitive to make doing something like this even easier. It is nuanced. This is the basic idea of what that's going to do, whatever that is.

Audience Member: [18:18] Essentially, you're trying to just wrap anything. We're just trying to create resources for anything that could be loaded by the browser. We're taking more control over it so that we can...

[18:31] [crosstalk]

Instructor: [18:31] We don't want to render anything that's not ready to be shown, basically. Images are a special case. That's why we have an exercise for them is because you can render an image before it's actually ready to display. What we're doing is we're stepping between the browser and the user to make sure that we don't actually render the image until it's ready.

Audience Member: [18:53] Another way to rephrase that is the browser cache exists, but we don't have any control over how it's working. We can't make it work in sync with all our other asynchronous stuff. This is allowing us to bring it into the suspense world along with our data fetching and lazy loading in the components and everything.

Instructor: [19:12] Exactly. If there were some API window.images cache and that has the source, if that were a thing, then we could say, "Oh, it's not in there yet. Let's throw a promise that resolves when it is in there." But there's no API for that. That's why we're doing this fancy thing.

[19:35] One of the drawbacks of doing things this way is that with the old way, with the regular image, the data comes in and we can display it instantly. Then the image will come in later. Maybe that's an OK tradeoff for you.

[19:51] It would be even cooler if there was a way to say, "Let's go get the data. Let's get the image at the exact same time. We'll do them both at the same time. We'll go fetch the data, go fetch the image." Right now, we're fetching the data. Data comes back. That gives us the image URL. Then we fetch the image. It'd be cool if we'd just get them both.

[20:12] Thanks to the way that our URLs are planned out, if we go like this, we know exactly what the URL for the image is going to be as soon as the user chooses which Pokémon they want to show. It's just the Pokémon name is the name of the image.

[20:33] You might have an ID. If you can have some way of knowing what that image URL is, or maybe if it's dynamic, it's on AWS, you have something in between, a service that gives you a URL that is predictable based on the information that you have, then you can go ahead and make that request eagerly, as soon as the user gives you the information that you need. That's what the extra credit is all about.

[21:09] What we're going is I'm going to get rid of this image cache. We'll just use a regular image. I'm going to change our Pokémon resource a little bit. I'm going to make it so that the Pokémon resource has a data property that I can read to read off the data.

[21:28] Then I'll make it so that the Pokémon resource has an image property that I can use to read off the image for the image. These are both going to be suspending. They're actually both resources individually, but when I create the Pokémon resource, I'm going to create both of these things.

[21:47] Here if we come down to where we're creating this Pokémon resource, what I need to do here is we'll make this our data resource and return an object that has a data property. Then we'll need to have an image resource. That'll be our image property.

[22:04] To create that image resource, we'll do create resource, preload image. Then we need to have some way to preload that image based on the Pokémon name. You could copy-paste this and just replace that. We have a utility to do that that is right in here in the fetch Pokémon that will take a Pokémon name and give us back the URL based on whether we're doing things locally or on the database.

[22:39] Let's use that right here. Get image URL for Pokémon. We'll preload the image at the URL for that Pokémon name.

[22:55] Now the resource that we create and set into our resource state is going to be an object that has two resources, one for the data and one for the image. Then when we come here to render the Pokémon info, we'll get the Pokémon data from the data. We'll get the image from image.

[23:12] Remember, when we say create resource, that right then is when the request is made. Right away, we fetch the Pokémon, and right away, we preload the image. This all can happen.

[23:26] Remember, before we would fetch the data, we would render out our components, and that would have our image URL in it. Then we'd fetch the image. Now, we fetch the data and the image at the same time, all right when the user says, "Hey, I want to look at this Pokémon."

[23:47] With that, if we go back online, clear all this out, and we'll go slow again. Let me click on Pikachu. We're going to get a request for Pikachu and the data right at the same time.

[24:02] I think I'm sorting wrong. Shoot. Time, no. How I turn off the sorting? That's annoying. I wonder if I can turn that off and come back. Nope. I'm all messed up now. Maybe waterfall is correct. There we go.

[24:24] We go to get the data. Then we get Pikachu at the same time. It all loads really quickly. You'll notice that we actually get two requests for Pikachu. This one is for a preload. Then the second one is coming from this cache. We'll need to do a hard reload to get that experience again.

[24:45] Hopefully, that makes sense. The idea is as soon as you know all the information you need to be able to get the data, go ahead and start fetching that data. That's the whole idea. This is all is part of the render as you fetch concept as well. Any other questions about this?

[25:17] That was everything. Go ahead and write down the things that you learned right here toward the bottom. Then let me take a look at the outline and see if we're at a break time.

Audience Member: [25:35] One other thing that I just remembered, I don't remember who pointed it out. It was after exercise one or two. We had that warning or that error in the console. What did we do that made that go away?

Instructor: [25:50] That was exercise three. Here's the final version of that. To make that go away, we used the start transition. React gives us that warning because it's basically saying, "Hey, if you're going to have the user do something that results in something suspending, then the user's going to do something, but they're not going to see a response to what they've done instantly. There may be a delay there. You may want to do something or at least think about that." That's what the warning's there for.

Audience Member: [26:40] Kent?

Instructor: [26:40] Yes.

Audience Member: [26:41] I have probably a stupid question. Earlier where the approach was you lazy load the image but you load the content first so that the user has something to see and interact with. What we're doing right now is making the delay of some sort and making sure that everything loads in one go. This is more applicable in scenarios where the image is pretty small. Is that right?

Instructor: [27:14] Yeah. If you have a really large image that takes a while to show, then maybe it would be better to show a smaller fallback or something like that. An example of this would be on my blog. You'll notice that the images are just SVGs right from the start. Then they fade into the actual image.

[27:35] That's another approach to this same problem. It's pretty complicated. I don't know how it works. I'm just using a Gatsby plugin for that. There are various approaches to the underlying problem.

[27:48] Even this solution is basically putting things into the browser image cache before it reveals the actual image. Some voodoo magic that they're doing here because it's really cool.

[28:05] If it's a really large image, then maybe you want to show some other fancy fallback before you fade in the other image. There's also you could have a blurred image. I don't know if that really answers your question.

Audience Member: [28:21] I think it depends on what type of user experience we are targeting.

Instructor: [28:28] Exactly. When I was making the schedule, I accidentally left out one of the exercises. I'm looking at things right now. We still have two 10-minute breaks left to take. I'm thinking actually we're not going to take the break now. We'll take it after our next exercise.

[28:53] Let's move on to caching our resources. Let me demonstrate the problem first. Then we can look at the solution. Here if I go down too slow, close all this out, and I click on Pikachu, it takes a second to get that all loaded. Then it loads in. Then if I click on Charizard, it also will take a second to get that loaded. Then it loads in.

[29:23] If I go back to Pikachu, it's taking a second to load again. Pikachu's data hasn't changed at all. Why doesn't it just instantly switch over? Which is what our final version of this exercise is all about where you can go load the thing. Then you can go to a different one, load that thing, and then go back. It's instantaneous.

[29:46] We're going to store all of these values in a cache so that we can just reuse them as we go. This is not really anything specific to do with suspense. There's no new suspense things that you're going to be doing. This is something to be thinking about when you start dealing with suspense, which is why we're talking about it.

[30:09] This is exercise number five. Let me make sure and check the markdown file to make sure I didn't miss anything.

[30:20] One other thing I want to mention is that caches are one of the two hardest problems of computer science. There are existing caches on npm, all with different tradeoffs that you might be able to use. For our experiment, we're just going to use a object similar to the image resource cache. We're going to solve this.

[30:43] If you want to in the extra credit, I don't know if this is even all that useful. I may not even go through it. If you want to try and stick the cache in context, you can. You put state in context. It works. It's great. I probably won't go into the solution for that one because it's just regular context stuff. Nothing to do with suspense.

[31:06] This one, the exercise at least will give you some idea of how you could accomplish caching things. Any questions before I set you off on this one? Then have fun! People are coming back. Let's see. LOL, not very funny. Kind of funny. [laughs] That's great. This one's good.

Audience Member: [31:49] Can you post that in the chat? I want to send that. One of my coworkers is a huge...

[31:55] [crosstalk]

Instructor: [31:56] Sad story. I actually have a streaming PC. This is separate from the Mac that you're looking at. I have this special software that lets me use my mouse on both. That special software has stopped working just out of nowhere right now. I can't type. I can click, but I can't type anything. This is what it is.

Audience Member: [32:18] [laughs] Got it.

Instructor: [32:22] Let's go ahead and work through this one. Basically, instead of creating a new resource every time, we want to see if we've already created a resource for that one and use that one instead of creating a new one. That's the idea here.

[32:36] I'm going to make a function here called get Pokémon resource. That's going to give me the Pokémon name. I'm just going to say, cut this out, get Pokémon resource, new Pokémon name. Then we'll paste it up here. Return that. Right now, that was just a straight-up refactor. We've just moved where that logic is happening to right here.

[33:10] Now, instead of calling create Pokémon resource, what I'm going to do is create a Pokémon cache, Pokémon resource cache. That Pokémon resource cache is just going to have the key be the name of the Pokémon and the value be the resource that was created for that Pokémon in the first place.

[33:32] We'll say let resource equal our Pokémon resource cache at the Pokémon name. If there's no resource there, then we'll create it. Resource equals that. Then we'll put the Pokémon cache at that name, resource. Then we'll return the resource. That's it.

[34:05] Then we come here. We're on number right here. Let's go online. Pikachu, that takes a second. Then we go to Charizard. We go back to Pikachu. It's instantaneous between these two. Ta-da! You'll also notice we have the time that these were fetched just displayed right there. That time never changes anymore.

[34:31] That will be the same because that is the exact same resource, the exact same data. What questions do you have about this, conceptually or otherwise?

Audience Member: [34:45] I have a question. What happens if you want to persist it through a refresh?

Instructor: [34:52] Good question. To persist through refresh, you'd use the same techniques that you'd do for persisting anything. Maybe you'd put it into local storage if it's not really sensitive data or maybe you just wouldn't. You would send it to the backend and have them refresh the cache over time.

[35:17] It really depends on the type of data. Just because we're using suspense doesn't really change how you would think about implementing something like that. It's just a matter of what it is that we're caching. Here we're caching the resource which has access to the data where normally you'd maybe just cache the data itself.

[35:42] If you wanted to persist it locally, then you probably just persist the data itself. Then when your app loads, one of the first things it would do is prime that cache back up with whatever was in local storage. That would work pretty well probably. Any other questions?

[36:06] I'm not going to take the time to do the context thing because context is context. This doesn't change anything. I'll show it to you really quick.

[36:17] What we do here is we still have that resource cache. We still have this get Pokémon resource. None of that has changed. What does change is we have this Pokémon resource context. We have this use Pokémon resource that uses the use context thing.

[36:33] Then down in our app, we render that provider with the Pokémon resource, the is pending state, and the Pokémon name. I took that Pokémon info, all of that, what we were rendering there, we put it in a separate component just to demonstrate passing it through without passing the prop.

[36:54] We use the Pokémon resource. We're just using context. That gets us back all the stuff we need for our work to work. Just a regular context situation here. Nothing special. Any other questions before we move on?

[37:16] We are going to take a break now. It'll be a 10-minute break. Then we'll come back to talk about suspense with a custom hook. Before you take that break, make sure to write down the stuff that you learned through this exercise, so you don't forget it.

[37:29] We'll come back at 15 after the hour, that's 3:15 for me, just in 10 minutes. Then we'll work through the next exercise, which is creating a custom hook for all this, which is great. See you in a little bit.

[37:45] It's about time to start. I wanted to show you this. This one was pretty good. Start with my failing test. I remove some assertions. Then I remove some more. All tests passed! Hooray! I thought that one was pretty funny. This one's good too.

[38:04] Let us go on to exercise number six! This one, there's actually no change in the behavior. This one's just let's change the code around a little bit and make it reusable so that we load up Pokémon resources all over our app! Why not? I think it'd be cool.

[38:24] That's the idea behind this is let's refactor this so that the logic around starting the transition, setting the Pokémon resource -- whenever this Pokémon name changes, we just get a new Pokémon resource -- just put of all that into a custom hook. There are a couple of nuances with this though. I mentioned them.

[38:50] There are actually two bugs that you might run into that are currently being tracked and will hopefully be fixed. I reported both of these thanks to creating this context. There you go. We're making React better.

[39:07] You could probably do the use memo thing. I can't remember who it was that mentioned that. I think it was maybe Tom brought up earlier. You might be able to make use memo work.

[39:16] If you want to use an effect, then you have to use layout effect. You can't use effect currently. Then you also can't include the start transition function in your dependency array. Just ignore [inaudible] there. That will be fixed before this is released stably. This is our goal. We just want to make a hook that does that.

[39:38] I'll give you a little bit of time to work on this one. We are coming down on our time. I know that for some of you, you expected his to be quite a bit shorter. Sorry about that confusion there. We'll give you a little bit of time to work on this one. We'll come back and do it together. Any questions before I set you off on this one?

[39:56] OK, let's go. We can look at a couple of Mark Dalgleish recent memes. That one's fun. This one's hilarious. I love this one. CSS, oh no! The platform is way worse than a fluffy bunny, I promise. This one's funny.

[40:23] Mark is way into the crossover between design and dev. Lots of his memes are about that. React [inaudible] to be 14 because we didn't have anything between those two. That's fun. This one's kind of fun. [laughs] That one's good.

[40:48] Welcome back, everyone! How do we make this a custom hook? Let's just create a Pokémon resource hook here. What are we going to need? We'll need to the Pokémon name. What's the API we're looking for? We need to return a Pokémon resource and is pending. Let's just stick that right there.

[41:12] We're no longer going to track the Pokémon resource or is pending. We actually are just going to move that up to our hook. We'll return the Pokémon resource and is pending. Now, we no longer have start transition or set Pokémon resource in here.

[41:30] What we're going to do is anytime the Pokémon name changes, we're going to do this stuff. We're going to do that in a React use layout effect pretty much only because right now there's a bug around using use effect, so we'll use layout effect instead. We'll move this stuff up into there.

[41:52] With the way that this has been written, when the app is initialized, there is no Pokémon name. It's falsy. If that is the case, if Pokémon name is falsy, then we'll just return. We'll exit early. We don't need to do any transition.

[42:11] Here this is going to be Pokémon name now. Let's add our dependency list so that we don't get this running on every single transition, Pokémon name, or on every single state update.

[42:23] Then there's that other bug that I mentioned in the docs here where normally you'd have to include this start transition, but we're not going to. Eslint disable next line React hooks exhaustive deps. There's a bug in React. Anytime you do this, there needs to be a comment explaining why you feel like you're better than Dan Abramov. Just kidding. But don't do this very often, very, very rare.

Audience Member: [42:56] The bug is that's it's giving a new start transition every time?

Instructor: [43:02] The bug right now actually is it shouldn't give you a new start transition every time, but it does. That's the bug that they're going to fix. In the future, you should be able to just do start transition. It'll be fine. It'll be like putting set Pokémon resource in here. It's consistent between renders, so it doesn't make a difference to include it or exclude it.

[43:27] Potentially, the extensive deps rule might be made smart enough to know that start transition is coming from use transition, so it doesn't need to be in there either. You should never need to put a start transition in your dependency list. In the future, it won't harm you if you do. Now I can get rid of this stuff.

Audience Member: [43:58] Kent, quick question. Use layout effect, I don't think I've ever used that. It happens. What's the difference again?

Instructor: [44:05] The difference between use effect and use layout effect is in the timing of when they run. When you update the DOM, the next line of code that runs after you do that DOM update, whether it adds a class or whatever, is going to run before the user sees the changes that you've made to the DOM.

[44:27] You may add a class. The next line of code can run. It will have that class. The user hasn't seen the effects of that class on that element. That is called browser painting.

[44:40] After all of your JavaScript has run, then the browser says, "Oh, I have new DOM! Let me paint it." The difference between use layout effect and use effect is use layout effect runs before that browser paint and use effect runs after.

[44:54] The reason that most of the time you're going to be using use effect is because most of the time the code that you have running doesn't need to run before the paint happens. We want to get to the paint as quickly as possible. We just offload all that to happen after the browser paints.

Audience Member: [45:14] Got it. Thanks.

Instructor: [45:17] That's all. Let's go back here, make sure that works. Pikachu and Charizard and Pikachu, woohoo! We're good. Solid. Any questions about making custom hooks that suspend? We are suspending right now here. Boom, we trigger a suspense change in our effect.

[45:43] This is where things start to get really powerful with suspense. When you can put the logic behind the resources that you're making into hooks, then maybe you can put it into context. All over your app, you just have code that loads.

[46:03] The thing that triggered the code to load also triggered the data to start loading. You get a much faster app. Then you also get the developer experience benefits of a much simpler way to think about structuring your asynchronous code, which I think is awesome.

[46:24] We've got 30 minutes left. I'm going to give you an option here. First, let me describe this last exercise. Then I'll give you the option. For this last one, we've got a bunch of these Pokémon. Going to shrink that down. We'll do this. Whenever we hit go.

[46:51] We go and fetch these Pokémon again. It looks like they're loading at the same times. Really quick, let me make sure we've got our delay in there. No, they're not. Let's refresh. When I click go, they all pop in when they're ready.

[47:16] The thing that we're going to be talking about is suspense lists and how that helps you coordinate when there are multiple components that are suspending. You've got a chat list. I want to make sure that the bottom one loads first and we load backwards like that.

[47:37] Or I've got a timeline. I want to make sure that things load in from top to bottom. Even if the data loads in a different order, I don't want to have things popping around all over the place. This happens a lot when you have a really bigger app.

[47:52] You start code splitting things a lot. You start fetching the data. Things just start popping into place all over the place. Suspense list allows you to marshal that in a way that makes the user experience better. That's what we're going to be talking about.

[48:07] This is one is a really quick exercise and then just playing around with stuff and experimenting with the different options. The question is do you want to have 5 minutes to do that yourself or maybe 10 or 15 minutes to go through the exercise and then play around with it yourself? Or would you rather just watch me work through some of this stuff and ask questions as we go?

[48:33] There's not a whole lot to the exercise portion. It's mostly just playing around with stuff. If you'd rather do the exercise yourself and play around with things yourself, say yes. If you'd rather just watch me do it, say no. Looks like the nos have it. Very negative! Just kidding. [laughs]

[48:58] I'll just work through it really quick. Let me just jump in my markdown, make sure I'm not missing anything.

[49:08] Here's the basic API for suspense list. This is straight from the React docs. You have multiple components in here that are all suspending. They're doing different things. Could be pictures. It could be the user's list of posts and then a specific post. It could be lots of different things.

[49:25] Suspense list is something you wrap around the component that suspend. They can be nested as well. You could have a div right here. It doesn't have to be all on the same level. Everything that is a suspense component beneath the suspense list will be managed by the suspense list.

[49:51] There are a couple of props. You have reveal order. If that's excluded or set to undefined, then that's the default behavior where things pop in when they load. If you say forwards, then they'll appear in the order that they appear in the DOM. If you say backwards, it's the opposite.

[50:08] Regardless of when the data loads, they just appear in reverse order. Together, they don't appear until they're all ready to go. Then there's the tail prop. We'll get into that one and then the children there.

[50:27] Let's go ahead and implement this. Down here in our app, we have this handle go click that starts our transition to create new resources for all the Pokémon that we like. Right here is where we map over those. We have our suspense for each one of these. I'm going to wrap all this stuff in a React suspense list and move this all in there.

[50:55] So far, we've done nothing useful. We haven't defined any props for reveal order or anything else. This is going to behave exactly as it had before.

[51:08] If we want to control things, we could say reveal order is together. Now if we come back here, we click go, you'll notice we're getting a bunch loading. But there are a couple that are extra slow. None of them show up until everything's ready. That can be useful sometimes, but for this particular situation, I don't think that's what we want.

[51:32] Let's try backwards. We just start here, going back, boom. The reason that they all appear to load together is because this was the bottleneck right here. New was really slow. Here, I'm going to come up here and change new to be a little faster. We'll say 500.

[51:55] We'll actually have a load in order, so you can watch that happen. 600, 700, 800, so then we go here, and it goes boom, [makes noise] , like that. Depending on the order of things, if this one takes extra long -- we'll say it's 1,200 -- then we can get these ones going pretty quick. Then boom, the rest of them go.

[52:24] That's basically how that's going to work. Then if we come back down here, that's all of them. You have together, backwards, and forwards. Then you also have the tail prop. You tail prop allows you to control the loading states.

[52:46] If there's a suspending component that is on the fallback, this controls which of those loading states you show. By default, we're going to get just the, all of those loading states are showing. If you say tail hidden, then what you get is none of those loading states show at all.

[53:13] No loading states show. Then if you do tail is collapsed, then the next loading state -- the suspense component that should be the next one to show up -- it will show the fallback for that one. Here, we get this is the last one.

[53:33] Then we get the next-to-last one, and then all the way through like that. That's what tail does. I think a natural question to ask at this point might, "What's the use case for all of these different things?" It's a little hard for me to come up with some things.

[53:53] I can think of a couple things for it, like reveal order backwards. You've got a chat, like I said. Forwards, on Facebook you have your timeline. You want to show the chat box thing. Not the chat box. Whatever that thing is. I never go to Facebook. The thing where you type in your status, your status bar.

[54:18] Then you show the things in your timeline after that. You also have images. If you have a gallery, then having them all just pop in, especially when you don't know their width and height, can be problematic. Having them go in a forwards order can be really useful.

[54:36] Collapsed might be useful for that scenario as well, if you don't want to just show a giant page full of spinners and stuff. That could also be useful. This mostly becomes useful when you wind up splitting a lot of code and data into things that are all going to load asynchronously and want to just coordinate the loading of those things.

[55:08] What questions do you have about Suspense list?

Audience Member: [55:16] I'm really curious about how it reconciles the order when the nesting can be of varying depths. I assumed, when I first saw the API, that the only children that would be allowed would be Suspense elements as direct children, but it looks like you can nest them.

[55:33] I'm curious how that works if you were to dynamically prepend things. It's an edge case situation, but it's got me curious. I don't know if you've experimented with that much.

Instructor: [55:44] I'm actually curious as well. Let's try this. Timer. Let's return time. We'll say, "React useState time setTime. Start at zero." Maybe this is more of a counter. Then we'll have a React useEffect that setInterval. I know this isn't the right way to do setInterval. Dan would be mad, but what do you do?

[56:21] Every 100 milliseconds, we'll set time to just increment by one. We'll just assume this never mounts, so we don't need to clean up. Then if we put the timer inside of here for each one of these, let's just take a look at what it does.

[56:41] It's not like those things are suspending, because those are not within the suspense boundary, but what the suspenseless does is, any time there's a suspense component within it, it's going to control how that suspense component, when that suspense component's fallback shows up.

[57:02] Based on the tail, it'll determine when the fallback switches to the, or when the Pokémon info or the suspending component is re-rendered, or when the suspense component itself is all re-rendered. It doesn't do anything special to anything special to anything other than those suspense components. That's it looks like to me.

Audience Member: [57:27] Right. I was thinking more like, if it's totally dependent on the source order, what happens when you are adding new suspended components in random places. It's like a theoretical curiosity. We don't need it dive into it.

Instructor: [57:44] If the timer itself had a button, and you could add a new suspense component while others were loading, and that kind of thing.

Audience Member: [57:51] Sure.

Instructor: [57:53] That's interesting. The React core team and the React code base, they have a test for the suspenseless component that actually is pretty readable. I don't think that they had any test for adding new suspense components while things were in flight. That might be an interesting one to add and just see what happens.

[58:17] Any other questions or interesting thoughts and observations? OK, cool. I'm able to hang out for the next 15 minutes to just answer any questions about things in general. Before you decide that you're not interested, just want to go to sleep, or whatever, I would invite you to please fill out this feedback.

[58:44] Now, the feedback that's in your outline.md file in your project is wrong. It says RP. It should say RS. Now, I would copy and paste this over to our chat, but I can't type right now. Oh, now, sweet. Rats.

[59:01] I can paste it, because I can use my mouse to do that, but I can't type to hit the enter key, so that's too bad. [laughs] If you can just type that in and fill that out, I'd really appreciate that to make this workshop better.

[59:20] Yeah, happy to answer any questions that you have. Thank you, Zack. Yeah, that's right.

Audience Member: [59:30] Testing, what have you found for testing suspense...?

[59:34] [crosstalk]

Instructor: [59:37] Actually, I have a lesson on testing JavaScript.com, at least on the update that is happening very soon, that shows you how to do that. Basically, suspense is an implementation detail, and you shouldn't need to worry about it.

[59:55] So long as, if you're testing a component that suspends by itself, like for example, if we were test the Pokémon info component by itself, then what you do is you'd render it in your test with a suspense component.

[1:00:11] Like when you say render this thing, you're just wrapping it in a suspense component. Then you could have a loading fallback or whatever. Of course, you'd need to pass your own fake Pokémon resource, but it would be just like a regular asynchronous test, regardless whether it's suspense, use effect, or whatever else you're mocking out. HTTP requests and that kind of thing.

Audience Member: [1:00:39] Got it. Like a test harness with a wrapping suspense component?

Instructor: [1:00:46] Yep.

Audience Member: [1:00:47] Cool.

Instructor: [1:00:47] I'm not sure what the story would be like with Enzyme, because they like to test implementation details. You'd probably have to mock a million things, but I don't know you'd do with the Enzyme. With React Testing Library, it's really quite straightforward.

Audience Member: [1:01:02] Is it any different for React Hooks Testing Library, or is there...?

Instructor: [1:01:09] That's a good question. You definitely would need to have your test component wrapped in a suspense boundary. I don't know. I can't remember. React hooks testing. I'm not maintaining this one any more. Let me just double-check if they have a wrapper. It looks like there's no wrapper option.

Audience Member: [1:01:35] Yeah, there's a wrapper thing. You just wrap that in Suspense.

Instructor: [1:01:39] Oh, you do have a wrapper option?

Audience Member: [1:01:41] Yeah, there is.

Instructor: [1:01:41] Oh, it looks like they've put that right here. What? They've got a website all their own? What is this? I'm just kidding. API reference, here we go. Wrapper. You'd use the wrapper and put a Suspense component in there. I don't know about React hooks, but certainly with react-testing-library having a custom render method is recommended. Setup here.

[1:02:17] You create your own custom render that has a wrapper. That wrapper renders all the providers that you need. You'd put a Suspense in here, maybe an error boundary, maybe. For all that stuff. Then you wouldn't ever have to worry about Suspense one way or another.

Audience Member: [1:02:39] On that same line, I know in react-native-hooks-testing-library you have this idea of waiting for the next tick or waiting for the next render. Does Concurrent change the next render, or are the expectations going to be different now?

Instructor: [1:02:56] The act function that is exposed by react-testing-library and react-hooks-testing-library -- I'm pretty sure react-native-testing-library also exposes this -- this is actually just a re-export from react-dom/test-utils.

[1:03:15] A huge reason why they added act function was so that you could say, "Here in this callback, do all of these things. When this callback is done, I want everything to be flushed, or I want all of the Concurrent Mode halfway finished renders to just be done. I want to flush them all synchronously so that the next line of code can make assertions on that." You shouldn't have to worry about it.

[1:03:48] Today, if you try to render, you can't actually use react-testing-library with Concurrent Mode because react-testing-library calls ReactDOM.render rather than createRoot. There's an issue open for this right now, for adding this feature.

[1:04:06] I just want to be careful about how we do this. My thought is what we're probably going to do is if your app is using Concurrent mode then your test should be as well. If your app is not using Concurrent Mode, then your test should not be.

[1:04:22] Your app is either using Concurrent Motor, or it's not. There are ways to make different roots, but not many people do that. What I'm probably going to do is we'll have an environment variable that you set or some global configuration that you set or both, to say, "I want all of my renders to be in Concurrent Mode or not." That's probably how it's going to be.

[1:04:44] Your tests technically shouldn't have to change one way or the other. React-testing-library wraps all interactions inside of act. All of the asynchronous utilities, all of these are going to be wrapped inside of act as well. You shouldn't notice any difference, hopefully. That's the beauty of implementation detail-free testing. Yay.

Audience Member: [1:05:14] I was wondering about...You mentioned earlier that you're aware of three or four different libraries for data fetching that "support" Suspense. I feel like the React documentation is a little bit light on that.

[1:05:32] They link to code box examples that do very similar things to what we're doing with wrapping promises and throwing them based on the status of whether they're resolved or not. Are you aware of any other libraries that are trying to support this at the moment? I'm only aware of this SWR one that I heard about recently and Relay. Are there any others?

Instructor: [1:05:54] Yeah. There is the SWR. Let's pull that up really quick. React Async has experimental support for Suspense. React Query is another one made by an awesome person who apparently hasn't updated his profile picture.

[1:06:16] Yeah, Tanner Linsley has made a ton of stuff. I'm intrigued mostly just because I know he makes really great stuff. Then SWR. There was one other. Oh yeah. Oh shoot. What is it called? Ah shoot. I can't remember what it was called. Just another experimental one. I'm sure there are many others already that people are playing around with. It seemed pretty cool.

[1:06:49] Those are some to check out, React Query, React Async, and SWR. I've used React Async in the past, before Suspense. It has a bunch of hooks and things for making async stuff easier. React Query is brand new. SWR is brand new.

[1:07:07] I know that React Query relies on Concurrent Mode and Suspense. I don't know if it supports it without Suspense. I think SWR doesn't need Suspense. React Async doesn't either. It's cool stuff.

Audience Member: [1:07:26] Awesome.

Instructor: [1:07:28] Any other questions?

Audience Member: [1:07:37] Any idea when all of this might not be experimental?

Instructor: [1:07:41] When I was React Conf, I was talking with Sebastian and Andrew about that. My assumption is that if Hooks took...I think it was like five months or four months to be stable, or maybe it was six months. Several months to be stable, to move from beta to stable. I assumed that Concurrent Mode and Suspense for data fetching would take even longer, just because they're more impactful.

[1:08:14] Any time I talk to Dan about it, he's like, "Everything's going to break. I'm not sure if anything's going to work anymore." He would just say, "I don't know. Maybe Redux won't work with this, and they'll have to be reworked. Maybe all these others might not work well. I'm just not sure. We'd need to see what people do with it."

[1:08:38] My impression was that things weren't going to be released for a while, but Sebastian said, "I don't know. We've been using it at Facebook for quite a while. It's working really well. This is something we've been researching and working on for years and years and years. I think most of the kinks are worked out."

[1:09:03] I can't give you any more of an answer than that, as little of an answer as that is. I don't know. It seems like it could be a couple months. I sure hope it's not a year! That would not be cool. [laughs]

Audience Member: [1:09:17] Create resource, Andrew hinted at it last year at React Conf I think. It's been over a year and a half now.

Instructor: [1:09:25] At the React JS Iceland, they introduced React cache, which I'm pretty sure is going to be totally abandoned. This doesn't work with current suspense. Maybe it'll stay at 666 open issues forever. That's interesting.

[1:09:47] [laughter]

Instructor: [1:09:51] This is React! [laughs] That's creepy. Somebody go make another issue on Facebook. Just tell them that you love React or something. [laughs]

[1:10:05] Maybe, but when I talked to Dan about it, he said, "I don't know about the React cache, if that's really going to be a thing." He told me that he was working on that create resource function that's similar to what we've been using and that's in the docs all over. He said that he likes that. [laughs]

[1:10:26] I was like, "Now, Dan, Redux started just like this." [laughs] He's like, "I made this little thing. I like it." Now it's the thing.

[1:10:39] I doubt create resource is what most people are going to be using in their apps. I'm confident that most people are going to be using a asynchronous-framework-type thing, like Relay or Apollo, or one of these libraries that just manages all those asynchronous interactions for you. That's where I think most people are going to be interacting with suspense at that level.

Audience Member: [1:11:06] My problem is I have a 10-year-old application which I think it'll be an uphill battle for changing how an enterprise works. In the meantime, if this was available, this would at least solve some of my problems. I do see myself using create resource a lot. Just curious.

[1:11:27] You briefly touched on how it would break Redux or how they're...

[1:11:32] [crosstalk]

Instructor: [1:11:33] I can't really tell you. I'm not sure. I know that anything that's using the unstable prefix, like unstable component will mount, is probably going to experience bugs with concurrent mode.

[1:11:49] The biggest problem for people adopting concurrent mode and suspense is if they are using those or if they're using libraries that are using those. In the React docs about concurrent mode, they actually say we plan on never upgrading the old facebook.com to concurrent mode.

[1:12:13] That might be easy for them to say because they're making a new facebook.com, [laughs] so they don't care to bother. That does say something. It's not just a flip the switch, like update your index like this. Then all of a sudden, you can be in concurrent mode.

[1:12:31] Existing applications will quite possibly run into bugs. Even this small application, I ran into bugs when switching. It was just because your renders aren't idempotent or you're using unsafe prefixed class methods.

[1:12:50] Lots of that is going to be in libraries. Libraries are either going to need to upgrade to stop doing that or people will have to stop using the library and make something new. It's not an Angular 2 experience where it's, "Rewrite all the things." It is going to be a bit of a upgrade path.

Audience Member: [1:13:13] It seems like it's stable enough to use the data fetching stuff as long as you're not using concurrent mode. Speaking personally, I care about the data fetching stuff much more than concurrent mode.

Instructor: [1:13:32] You can absolutely use suspense without concurrent mode. The thing that you won't get is I don't think that you can use transition. Any time you do a render that updates something to cause a suspense, you won't be able to have any interim state between now and showing the fallback. It'll just instantly show the fallback, which maybe that's OK.

Audience Member: [1:14:06] Is that going to be always the case permanently for sync mode?

Instructor: [1:14:14] I think so. It's just a fundamental architectural problem with sync mode.

Audience Member: [1:14:25] What are the performance gains from just changing your root from sync mode to concurrent mode?

Instructor: [1:14:32] There are certain interactions that you can do that work better in concurrent mode. Probably the best thing for you to go look at is JS Iceland Dan Abramov, his talk "Beyond React 16". There are two things going on here.

[1:14:58] Here's his little demo where he types in this input. Every single character that you type, it changes a bunch of stuff on the page. It's a really janky experience, especially the more you go, there's more data in there. Typing is no good. He has this demo. You should just watch this thing. It's fantastic.

[1:15:24] It shows you that the user interacting with the application is really impacted by how React is just so busy doing all this stuff. One way to solve this is to go with debounce. You type. After you're done typing, then things update. It's still not great because when it's time to update, it's really choppy at that point in time.

[1:15:50] Then switching to what at the time was called async React and now it's called concurrent React, React prioritizes the user interaction over the updates to the DOM because of the user interaction. The typing experience feels really great. It just fills in updates to the DOM as it's able. The app stays responsive the whole time. You don't have to worry about the janky experience for the user.

[1:16:20] Those are the types of experiences that you'll run into. Any time in your app you find that the user's doing something and React is doing something at the same time so the user's experience is slow, those kinds of things are going to be a lot faster with concurrent mode.

Audience Member: [1:16:39] If you have a universal app with SSR, I noticed that there's no hydrate equivalent that I'm seeing in the new concurrent mode.

Instructor: [1:16:47] That's coming eventually. There are absolutely plans for that. I can't really give you too much more. I don't really know much more than that. I know that it's a priority for them because now they are using SSR for facebook.com, the new one. It's a priority. At least I think they are. I'm pretty sure they are. We should hopefully see a lot more love for SSR, which would be great.

Audience Member: [1:17:14] That's good to hear.

Instructor: [1:17:19] Thank you so much for giving me so much of your time, especially those you who didn't realize that this was going to be five hours, [laughs] even more of your time than you expected. I hope it was really interesting and enjoyable.

[1:17:31] Please do submit feedback. I read it all. Appreciate it. Thank you. Have a wonderful rest of your day or morning or evening or night or whenever it is.

egghead
egghead
~ 27 minutes ago

Member comments are a way for members to communicate, interact, and ask questions about a lesson.

The instructor or someone from the community might respond to your question Here are a few basic guidelines to commenting on egghead.io

Be on-Topic

Comments are for discussing a lesson. If you're having a general issue with the website functionality, please contact us at support@egghead.io.

Avoid meta-discussion

  • This was great!
  • This was horrible!
  • I didn't like this because it didn't match my skill level.
  • +1 It will likely be deleted as spam.

Code Problems?

Should be accompanied by code! Codesandbox or Stackblitz provide a way to share code and discuss it in context

Details and Context

Vague question? Vague answer. Any details and context you can provide will lure more interesting answers!

Markdown supported.
Become a member to join the discussionEnroll Today