Use React Suspense to Simplify Your Async UI with Kent C. Dodds
Instructor: [0:00] Let get back to it. Hopefully you had a nice restful break. For this next exercise, we're going to be talking about this render as you fetch thing that you may have been hearing about. I don't know if you've been following the concurrent mode suspense stuff, because it's all...
[0:22] [crosstalk]
Audience Member: [0:23] I have a question.
Instructor: [0:26] Yeah, sure.
Audience Member: [0:26] Why is it that we have a react suspense wrapper on the main app as well?
Instructor: [0:32] The reason for that is because...Let's see, right here. React suspense loading. It's because of this isolated component. That's what's rendering your page. Your component is...We're actually using react.lazy, and react.lazy needs suspense to work. We're just doing code splitting.
[0:56] The reason that we're doing that is because I want your code during the exercise to be the only thing that's running on the page. If there's other code somewhere that's doing something different or messing up some global state or whatever, it doesn't affect your exercise.
Audience Member: [1:11] OK.
Instructor: [1:11] That's why. Actually, let me just show you. This is the idea. I actually tweeted this pretty much just to be able to show you today. We have two experiences here. Actually, I'll show you the thing that we're building first. We click on this, it loads the Pokémon. We click on Charizard, we click on you or you can actually type in anything, any other Pokémon and it'll show the Pokémon.
[1:45] The experience that as far as the network request go, this is what happens when you select one of those things. Assuming that the component used to display the Pokémon data is going to be lazy loaded, then this is what happens.
[2:02] You click on a Pokémon and it says, "Oh, OK. You lazy loaded the Pokémon display data thing. Let me go get that. That's going to be in a separate check." Once that comes back, then React will re-render. It says, "OK. The component's ready now. Let me render that component now."
[2:19] We load up a fallback image because now we're waiting for the GraphQL request. We load up the fallback image and we make the GraphQL request. That first request is an options request for course, just unfortunate. Once that comes back, then we can make the actual data request.
[2:40] When that data request comes back, then we actually need to re-render the data and render an image with a source pointing to the Pikachu JPEG. That will make yet another request.
[2:51] We have this waterfall effect where we're collocating our data request in a use effect or whatever. As soon as the component loads, then we have to load the data. We have all the information we need to request it all at once.
[3:10] That's what the render as you fetch paradigm or approach is all about is we don't need to wait for the code to load or even the data to load before we can load the image. We know all of the information that we need to load this all at once.
[3:29] The only reason we have this dependency right here is because we have to do the...Sorry. It's this dependency from that one down to this one is that this is the options request for course. If you're running this on your server or whatever on the same domain, then you don't need that. You'd literally just see all of this happening all at once.
[3:49] The difference between these two is one is twice as fast as the other. It's not an insignificant difference. This is a really insignificant app. Imagine a bigger app that has lots of data, dependent data, all those kinds of things.
[4:07] Sometimes, in a RESTful world, you have to go request the thing, it comes back with more information so then you request all the post by that user or whatever. You have this cascade that's built into the concept of REST, which is really unfortunate.
[4:25] If you're really a performance conscious, you might set up a server in the middle that implements GraphQL, so that you could actually avoid all that problem altogether. In any case, lots of the times, you know everything that you need to make all the request up front and that's the whole idea behind render as you fetch.
[4:45] That's what we're going to be implementing in this exercise. Any questions conceptually about this idea? OK. Cool.
[4:57] The key difference between these two is where the fetch request takes place. We really like to have it take place in the component that needs the data because there are a lot of maintenance benefits to that.
[5:09] If you need to make a change to how that data is used or query or something like that, like you're getting all of your props and everything. It's just is so much easier to maintain by collocating the query or the REST call or whatever.
[5:24] That just naturally leads to this waterfall effect. There's a way to get your cake and eat it, too, which we'll be doing as part of the extra credit for this.
[5:35] The first part of the exercise, the actual exercise itself, doesn't really give us the render-as-you-fetch approach, because we're not lazy loading the code. You can't really see the benefits as easily. It's when we do the extra credit is when you get into lazy loading that code and seeing those benefits.
[5:55] Let me just make sure I covered everything. That's pretty much everything. Looks like I had an incomplete thought right there. Whoops. In this exercise, we've got that form. Let me take you on a little tour of the code really quick, because things have changed a little bit.
[6:16] Here, this is in exercises, too. We've got a couple of things that we're pulling in from utils. We have those controls right here that you can control whether you're fetching from a remote server or locally. When you do your network tab analysis and stuff, I'd probably do fetching from the actual servers, so you can see what the request will look like.
[6:46] Now we have this Pokémon info component that is doing our Pokémon resource read thing. We have our image and then we have a Pokémon data view. That just displays all the data for us, so you don't have to have all of that code in here. You've seen that already.
[7:03] You can look at it later if you want to, but it's just like a regular presentational type component. Then we have our app. The app is maintaining the Pokémon name state. Also, we have this Pokémon resource. That resource we created outside of the component, now we're going to manage it inside the component.
[7:21] We're not going to create it on render, we'll create it when the user clicks on stuff, or when the user submits a Pokémon. That's what you're going to be doing in the handle submit, and then all the same stuff that you did in the last exercise, putting those error boundaries and Suspense components around here. That's all done for you.
[7:40] Your job pretty much is just to manage that Pokémon resource. Then for the extra credit, if you want to, you can refactor from useEffect. We have this same feature just built using useEffect. You could refactor that if you want to. For even further extra credit, let's go back here. We're going to have you lazy load.
[8:09] One other thing, the useEffect approach, this is called...Shoot. It's fetch-on-render. On render, then we fetch. You can do lazy loading of this component for extra credit. You can compare the two, and then you do a lazy loading on the render-as-you-fetch approach, so that you can get a comparison of those two approaches.
[8:38] That's all of the stuff for this exercise and the extra credit. It's a pretty cool one, lots of fun. Any questions, before I set you free on this one? Cool. Looks like people have been enjoying talking about REST in the chat. REST. May it REST in peace. Should have saved that for when everybody was coming back for my joke.
[9:05] I've got some good jokes for you later. I'm going to let you go. Have fun. All people are asking streaming in here. I figured we'd just go to Mark Dalgleish, his Twitter, because he's hilarious. That's how we'll keep ourselves entertained. I'm not good at jokes, but Mark sure is. He's way into design here.
[9:30] Let's see if we can find a really good one. That one's fun. He's been on this whole Sketch thing recently, and Figma stuff. Somebody sent me this dev joke repo that's just tons of dev jokes. O-O-P. Again, it's fun. Very good.
[10:09] Everyone's back. Here's the exercise. We want to make this thing work a little bit nicer. What I'm going to do actually, the first thing I'm going to do is restore the original fetch. We make an actual request. We can see in the network tab here. It's good. Slow, 3G. Clear all this stuff out. I click Pikachu.
[10:38] Actually, no, you can't really see that. We'll actually do the slow 3G thing when we lazy load this thing later. It's not even working. Let's get it working in the first place, and then we can get things were loading nicely.
[10:56] Here we go. We've got our app. We have this Pokémon resource thing. We want to create a resource to pass to our Pokémon info. Anytime the user submits a new Pokémon [inaudible] will update the Pokémon name so that we can use that in our fallback. We'll update the resource based on that Pokémon name.
[11:22] Let's make our Pokémon resource with create resource using that fetch Pokémon with that new Pokémon name. Now that we have this resource, as soon as we call this, it's going to eagerly go off and fetch that Pokémon.
[11:40] We need to pass this Pokémon resource to the Pokémon info so it can read the value. It will trigger a suspense right from the get-go. We rerender and it suspends.
[11:53] Let's say make this a new state. React use state. We'll start it out with null. This is going to return an array. We'll have set Pokémon resource. We'll call that set Pokémon resource with the Pokémon resource. That's that. That should work. Come back here. Get our refresh. Pikachu, ta-da. Nice.
[12:28] There's some extra credit and stuff that we're going to do here. What questions do you have about what we have here?
Audience Member: [12:42] I noticed a console warning. I was confused if that was because this is an experimental mode or not.
Instructor: [12:49] Great question. We're going to deal with that in our next exercise. Our next exercise is to deal with it. Since we mentioned it here, one thing that you'll notice is when I...I'll make it super big so you can have less chance. Whoa. Oh my goodness. Sorry, one second. Maybe not quite that big.
[13:14] As soon as I click on new, you'll notice that the search input doesn't update right away. It takes a second. Click, and just a couple of milliseconds later, it loads. You'll notice it actually is a little bit worse. Refresh. Come on. There we go. If I switch my network to slow, then it actually takes even longer. Maybe not that much longer.
[13:46] Anyway, it takes a second before this gets a rerender. That's what this warning is talking about and that's what we'll solve in our next exercise. Great question, any other questions? Sweet.
[14:04] Let's try out refactoring from useEffect. Quick question. Did anybody do this, did anybody try refactoring from useEffects?
Audience Member: [14:16] Not yet.
Instructor: [14:17] Not yet.
Audience Member: [14:18] Sorry.
Instructor: [14:18] I'm going to do a quick poll, just because I'm not sure how valuable will be to refactor from useEffect. Say, yes, if you want me to refactor from useEffect using the voting system thing, and no, if you'd like me to move on. Looks like most people want to watch this.
[14:39] It's going to just result in what we have at the end here, but watching the process could be interesting. Let's pull up the effects version of this file, so we can have that up and going.
[14:52] You'll notice it's giving us the exact same experience, pretty much, the exact same experience as Suspense. It's a little bit different because Suspense will delay. Here, I can show you the difference between Suspense and what we have currently.
[15:12] We're going to come here. I'm going to disable our real fetch, we'll use a fake fetch, and we're going to make it take five seconds to resolve anything for getting the Pokémon. With Suspense, we initially show the loading screen and then we resolve to this stuff.
[15:37] Then, if I choose the second one, it just waits for a second before showing the fallback. The reason that they do this is because if your asynchronous thing happens quickly, then there's no reason to show the fallback. We'll just go straight from one to the other.
[15:57] Taking another example, if you're on a fast network connection and it's just 100 milliseconds, then we go boom, and go boom, and go boom. Whereas on this one, if we make this take 100 milliseconds, then we go running screen, see that experience?
[16:17] That's the difference between what Suspense gives us out of the box, and what the traditional useEffect thing is, as you get this flash of loading state. Other than that, everything else is going to be the same.
[16:29] We could accomplish that same thing with React useEffect. I remember doing that same thing even with AngularJS. It was not easy, but it was possible. React team just saw this as an opportunity to have a better default. That's going to be the difference when we're all done.
[16:53] Here, all of this stuff that we're doing is all managing that loading state. It's not until we get down to the success area here, that things are dealing with the data that we've loaded.
[17:07] Typically, you're going to find that you have put lots of this stuff in some hook or something, cause some hook to make it easier, so you don't have to copy, paste all this stuff everywhere.
[17:20] Let's refactor this over to Suspense. The first thing that I'm going to do is, I'm just going to get rid of this error and just use the error boundary right here. Let me grab that. Come down here and wrap this in an error boundary. I can't remember, do I put that within or without? Yeah, we're going to put that right directly around the Pokémon input info here.
[17:44] That's going to take care of my error state. We can iterate our way to the error by just saying throw error, and that will be caught by the error boundary. This was just a straight-up refactor. There's no real change in the user experience with what we've just done. There's no need to have concurrent mode turned on for any of that either. This is all, you throw an error, it's caught by an error boundary.
[18:12] Here's where things start getting suspendy. If I want to have a fallback, then I'm going to come down here and we'll do a React suspense. Actually, I'll put that in the error boundary, suspense component with the fallback. Put my fallback right there, and then put my Pokémon info inside of that. We've got our Pokémon name for the loading state thing there too. Cool.
[18:41] Now, I'm going to go ahead and get rid of both of these. I'm going to get rid of the status. I'm going to just assume that we have our data. We'll get rid of all this management stuff. Instead of getting the Pokémon name, I'm going to get the Pokémon resource. I'll get my Pokémon from that Pokémon resource.read.
[19:11] All of this stuff is going to go away -- the set state, all of that nonsense is all just going to go. What I'm left with is this fetch Pokémon, which I do have to do somewhere. That is going to be happening in here. I can't just say fetch Pokémon. I need to make a resource out of this so I have that read API.
[19:32] I'm going to say create resource fetch Pokémon. That's going to be my Pokémon resource. I'm going to get create resource from up here from my utils. That's that function that we wrote in the last exercise to give us that read API that we're looking for.
[19:52] I need to have some mechanism for getting this resource down to my Pokémon info. I'm going to have a state here for Pokémon resource. Start that out at null. Here will come with set Pokémon resource. I'll just wrap that like this. There you go.
[20:16] From now on, instead of basing things off of the Pokémon name and passing that along, we'll actually pass the Pokémon resource instead. Those two things are now identical in both behavior and...It looks like they're not working.
[20:35] Hold on a second. Let's figure out what's going on here. Pokémon resource. Pokémon. New Pokémon name. There we go. We were using the old Pokémon name, so setting it to undefined, which is doing nothing. Now that's going to work. Ta-da. Nice. Cool? OK. Any questions about this?
Audience Member: [21:05] Yeah. I was wondering just on the storing the research in a state, would it make sense to instead use an ML to create it and apply based on the Pokémon changing so you'd use that as a dependency?
Instructor: [21:21] I see what you mean. We have our Pokémon resource equals react.useMemo, and then this will be based on the Pokémon name, and then this will be create resource. That might work. Let's try it out because this is similar to the experience that I had when I was having trouble with the...
Audience Member: [21:47] This, you said you could start with that naming, right? You deleted this...
Instructor: [21:51] Yeah, you're right. Sorry, there you go. Thank you. Then we don't need to manage that anymore. This one can be one name, there you go.
[22:05] Let's see, actually, we don't want to create the resource if there is no Pokémon name. If not Pokémon name, then return null. Otherwise, will return this. There you go, something like that.
[22:22] I think we're missing something. What is this? I think my formatting's messed up. There we go. Cool. This might work but it also might fail spectacularly. Let's see. No, that looks like that works.
[22:43] When we get to Exercise 6, we're actually going to be making a custom hook and that one uses useEffect, but you might be able to do the same thing with useMemo. The nice thing with this is, if there are multiple places in a component that can update that Pokémon state, then we can manage that.
[23:07] One drawback to this is useMemo, there's no guarantee that your useMemo callback will not be called if Pokémon name doesn't change. It could be called, if Pokémon name does not change.
[23:22] From what I understand, the reason for that is if the user goes off-screen from your component, then React could potentially free up some memory by getting rid of the memorized version of whatever it is that you're returning. I think that was what it was.
[23:48] Anyway, in the docs it says, useMemo doesn't guarantee that your callback will only be called when these changes. There is that, but I know their approach. Like I said, I couldn't tell you which is better or worse, because I have never shipped anything with this, but there you go.
[24:06] Any other questions?
Audience Member: [24:08] Do you see some useful hooks coming out of this? Whatever the API is, you can just have a generic API, which says, get me an object of this type, and then pass the props in and let that figure out the utilization of the resource and how to get the creative resource, etc.?
Instructor: [24:26] Yeah. Boom, this is exercise six, spoiler alert. Yeah, we're going to do that. Absolutely, this is the future of Suspense is you'll just have a hook that gets you the resource. It'll be magic. It's going to be so cool.
Audience Member: [24:41] Never mind, then.
Instructor: [24:44] Let's go ahead and do a couple of other extra credit here just to compare our typical fetch on render versus render as you fetch. For the fetch on render, what I'm going to do is I'm going to undo all the changes I just made. Let's get this, turn it all around this to just undo everything. We're back to our use effects approach here.
[25:11] This is render as you as you fetch. I'm sorry, this is fetch on render. That terminology is really hard to nail down. When we render, then we fetch.
[25:25] I'm going to get rid of this. We're going to assume that this is going to be lazy-loaded. We're going to get Pokémon info from a lazy-loaded component. We just happen to have such a component here, that exact same thing copy-pasted.
[25:43] I'm going to do a react.lazy to get this Pokémon info. We'll render that instead. Here, we'll get Pokémon info=react.lazy import from lazy and fetch on render. Boom. Then we can get rid of this stuff and fetch Pokémon.
[26:10] You can imagine this being something you're doing in a router or something like that where you're just going to go get that stuff. Because you'll like maintainable code bases, you're going to do all the data gathering, collocated to the component that is doing that data gathering.
[26:28] With that, now, we go here. I'm going to clear this out. We'll go to 3G. We'll hit Pikachu. Now we're going to fetch that component, then we're going to fetch the fallback, then we're going to fetch the Pikachu JSON because we're actually doing all of our data requests locally.
[26:47] Here, let me switch this. This is the time we want to do an actual data request out to a server. Let me go online. I'm going to do a hard reload with empty cache and everything. We go slow 3G, let's do this again. Go get the code. We get our fallback. We'd go get the data. Then we'd go get the image that we got from the data.
[27:17] That's how we wind up with this waterfall, because the fetch happens after the code is loaded. Compare this to if we take this, remove the Pokémon info. We're going to make it a React.lazy component. That's import from lazy, and then Pokémon render as you fetch. Whoops. There we go.
[27:56] Let's take a look at that really quick. No magic on my sleeves. It's just we're putting that file in there. Then one other thing, we don't want to lose our data colocation benefits. That's why we do fetch and render, so we can colocate where that data is happening.
[28:16] What I'm going to do is I'm going to get rid of this stuff. Here, let me just comment that out for a second so I don't forget to come back here. We're going to get the mechanism for getting the data from a file that's right next to the one that uses the data. This is a little bit of a step-back. Here, with our useEffect, it's all right in line. It's nice and easy to maintain.
[28:45] With this, we're going to separate the two because we don't want to load all of this code. There's a reason we're trying to lazy load this. Maybe this is using D3 or something. It's really big. We don't want to put the data fetching in here because we need to load that file for the data fetching before we load this file.
[29:09] We do want to have them close by. That's where we're putting them, in these two files. With Relay, they have a plugin that allows you to do all of your data-fetching code in the same file. Then they have a build command that will extract that to these separate files. This is totally something that you could do yourself with the babel macro or something like that.
[29:29] Here, we have our thing to get the data. We're going to import that. It will be included in the initial bundle of what our users get. Import creates Pokémon. What do we call that? Create Pokémon resource from lazy and then [inaudible] data. There we go.
[29:55] We've created Pokémon resource. Then we can come down here and set the Pokémon resource to create Pokémon resource with a new Pokémon name. Data fetching still happening locally to where the file is. That's doing all of the data display. We want to lazy load that. That's why we're doing all of this stuff.
[30:19] With that all in place now, let's do another hard reload at the empty cache. We'll switch this down to slow. We'll clear this out. We'll click on Pikachu. We got our chunk, and something parsing. Hold on a second. I'm going make sure I'm on the right page. Let me just figure out really quick what is broken here.
Audience Member: [30:49] In Pokémon info, you have .data.read.
Instructor: [30:56] In Pokémon info?
Audience Member: [30:58] The render-as-you-fetch02 file. I saw it.
Instructor: [31:04] Oh, right. That is a typo. My bad. That's spoiler alert for what we're going to do here in a little bit. Nice catch, Alex. Thank you. Whoever else it was that spoke, thank you. Let's go ahead and try this again. Let's do another hard reload into the cache, clear out this, go to 3G.
[31:27] Actually, let me make sure...Let's do a real fetch02. Let me go back to online. Save this. Do another hard rerun. Testing all this stuff is a little finicky, but this is the real-world user experience. They have no cache. They're on a slow network. We want to offload all the code that they're going to load in the first place.
[31:47] We are going to lazy load this, but we want to fetch the data as soon as we know the information that we need to get the data. I click on Pikachu and, boom, we load all three of those things all at once.
[31:58] It looks like we failed to load the Pikachu image for some reason. I'm not sure why that happened. In any case, we are getting the straight-down waterfall rather than the one we had here before. That's the idea behind render-as-you-fetch. It allows you to go get the data at the same time you're getting the code that's going to use the data.
[32:32] I expect that our routers will be how this built in where you can say, "Hey, on this route, I'm going to need this data." All of that is going to be awesome. It's fantastic. You probably won't have to do as much work as we're doing here. You just use a couple of hooks, and magic will ensue. It would be awesome. That's the basic idea behind this render-as-you-fetch. Any other questions about this?
[33:04] All right, that's all for this one. You open up the 02.js, scroll down, and there is the elaboration feedback. Write down the things that you learned. We're going to start our...Oh no, not the breakout. We have exercise three after this.
[33:24] Go ahead and write down what you learned, then we'll jump in to exercise three. It's a quicker one. Then we'll do our lunch break or whatever time it is break. Let's do exercise three. Let's pull up the exercise. Here are these other ones.
[33:48] We've talked about this a little bit before. When I click on Pikachu, then we get that warning down here. When I click on Charizard, then for about 500 milliseconds, the input stays with Pikachu rather than saying Charizard. It makes it feel almost like our app is unresponsive for that little bit of time. It makes it feel a little slow. It would be nice if we didn't have that problem.
[34:16] That is what we're going to be fixing here. The React team recognized that as a problem and created this useTransition hook for these loading states here. This is the way that you use it. You have this suspense config. You determine how long you want the transition to last before it goes to the loading state.
[34:42] By default, you know what I said, it takes a second. Then it goes through our loading state. Then it shows the data as it loads. Goes through our loading state, shows the data as it loads. Consider this as a navigation from one page to another rather than just updating some state here.
[35:01] If we go click on this button, it's going to take some time. We switch from the current page to the next page, and all that we see is just this giant spinner or something. That's not a super awesome user experience.
[35:16] It might be better is if when you click on this, you get a little spinner next to it or something like that that says, "Hey, I know that you asked for this. I am going to get your data." We show that spinner for four seconds. If it's still not loaded, then we can go to that other page with that big spinner or something like that. That's a much better experience.
[35:40] That's the goal of this useTransition, is it says, "Hey, I'm going to do a suspending thing. I want you to wait for four seconds before you show the fallback," or, "I want you to wait for three seconds." It allows you to control that.
[35:53] Even more, it also gives you this Boolean variable for displaying a spinner, or some sort of loading state, so you can indicate to the user that you're in the middle of some transition for the next four seconds, before it shows the actual fallback. That's the idea behind this.
[36:20] In the final version of this, I'll show you that, what we're going to get to. We start out here with loading Pikachu. When we click on Charizard, then it makes the existing Pokémon transparent, slightly. Then we can go to Meue, makes it a little transparent, and then goes to Meue.
[36:40] It doesn't show us the loading fallback, it shows this interim state. If took a long time, then it would switch to the loading fallback. It doesn't take all that long.
[36:52] In addition to this, you'll notice that the input value right here is changing. As soon as I click on this button, it changes to the value that it should be. It improves the user experience there. That's what you're going for, for this exercise.
[37:07] This is definitely a good one to play with the window.fetchTime a bit. You can see, "OK, if it takes this long, does it show the loading state?" Play around with that. There's not a ton for this one. There is some extra credit that gets interesting. Feel free to jump into that one.
[37:29] Before I set you off, anybody have any questions? OK, have fun.
[37:39] Let's work through this thing, to make the experience better. The first thing we're going to do is useTransition. That's going to come from React. We'll call that and we'll get a startTransition and it isPending. We could call this anything we want. If you had a couple of these in a single component, this could be startPokémonTransition. You can do a couple of these.
[38:05] What we need to pass here is our Suspense configuration. I'm going to create that right here. Suspense config, with a timeout milliseconds of 4,000 milliseconds, pass that in there. We're going to wrap this inside of a startTransition call.
[38:32] We say, "Hey React, I'm going to do something that's gonna suspend some component, and I want you to use this configuration for that Suspense thing that happens." That's what we're doing here. We'll save this. I'm going to add inline styles to change the opacity. We'll say, style opacity. If we're pending, then we'll do .6, otherwise we'll do 1.
[39:05] Let's go here. I select Pikachu, we show the loading state right from the get-go, select Charizard, and it blurs out, becomes transparent. The input is changing Meue. We have accomplished our goal. Hurray! That's great.
[39:26] Got a couple of other things we're going to do here. One thing I wanted you to play around with is, what if we put this setPokémonName inside of the startTransition? If you do that, then you wind up with pretty much the same experience.
[39:45] Hold on a second. That's not what I thought it was going to...That's strange. This should not be happening the way that it's happening. When I was playing around with it earlier, and when I wrote that Node, when I clicked on this, it would not change the input instantly, but looks like it is.
[40:08] I'll have to investigate that some more, because I'm pretty sure you only want to put stuff in here that's going to suspend. Maybe not, I suppose. That's strange.
Audience Member: [40:18] Are then handling it at a higher priority than everything else, or do you have to give explicit priorities for everything?
Instructor: [40:23] You don't have to give explicit priority. That's strange. Pretend I didn't say that. [laughs]
[40:32] I have a couple of extra credit things here. We're going to play around with the busy indicator stuff here. What question do you have about what we have so far?
Audience Member: [40:51] Why would you have multiple transition hooks in here?
Instructor: [40:55] That's a good question. What if we had a page that was rendering multiple components that could suspend? We have another thing right here, that's a thing that suspends. We're going to pass some resource to this thing. We're going to put a React Suspense around this. That'll have its own fallback.
[41:32] When we set this resource, we might want to have a different transition for that one. [inaudible] and all that.
Audience Member: [41:40] In a large enough application, you would most likely end up with only one per module, whichever preferable?
Instructor: [41:49] Typically, you're probably only going to have one. I wouldn't be surprised to see use cases where there are more than one in a single component. I don't have any production experience with this. Nobody does, except for a small team at Facebook. [laughs]
Audience Member: [42:09] Fair enough.
Instructor: [42:11] They may have situations where you have multiple. Any other questions? Let's...
Audience Member: [42:19] I'm sorry, I do have a question.
Instructor: [42:22] Sure. What's up, Alex?
Audience Member: [42:24] Can you explain the Suspense config number, like 4,000. What does it mean?
Instructor: [42:30] That's 4,000 milliseconds. Let's say our fetch takes 5,000 milliseconds, then I'm going to come in here, I click on this. The first one, it shows the fallback immediately, because that Suspense hadn't rendered yet. It shows the fallback immediately.
[42:48] Now that it's up, we click on Charizard, and our transition says, "For four seconds, we're going to show this interim state. If it takes longer than that, then we'll show the fallback." That's what that determines.
[43:02] If we change this to 500, then we come in here, this takes five seconds. We click on Charizard, and that interim state only shows for 500 milliseconds. That's what that's controlling, is how long are we going to reference the isPending thing, before we show the fallback instead.
Audience Member: [43:29] Is it fair to say that this is recovering a little bit of what we have with the useEffect data fetching, because it allows it to keep the old rendering of the component around?
Instructor: [43:41] With the useEffect, you had to write everything yourself. Normally, with mine, the way that I wrote it, you don't really have an interim state. You always show the loading state immediately. You could do that. Here we could say...I don't know, we can say, isPending, then show the same fallback. You could recover that same experience.
[44:18] The idea here is to say, "Hey, I don't wanna..." Suspense automatically doesn't throw away the old stuff until the new stuff is ready, or a timeout occurs and then it'll show the fallback. The default time, it looks like, something like 200 to 500 milliseconds for that to happen, between the time that it shows the stale data and it shows the fallback.
[44:42] This gives you a mechanism for controlling how long it shows that stale data. It also gives you a mechanism for knowing when it's showing that stale data, so that you can render something useful to the indicate to the user, that what they're looking at is old. That's the idea.
[45:03] Let's explore a couple of other situations here. If we go down to this...and I'm going to bring us back up to four seconds. If the loading only takes 200 milliseconds, maybe their user's on a fast Internet connection or something, then we're going to have situations like this. You see how it blurs, and then goes right to the result.
[45:29] It would probably be nicer if it didn't blur at all. Maybe the text in here updates immediately. We go straight from our...Here we can show this. If we don't blur at all, this might be a better experience. Otherwise, you get that flash of loading state. That's not awesome.
[46:00] That is what our extra credit deals with. The first part of the extra credit is, what if, instead of applying inline styles, we use CSS class that had a transition delay? That's what I have in here, in the style.css, we have this Pokémon loading CSS that has a transition delay of 3.3 seconds. If it takes 300 milliseconds to load, then we'll show that lower opacity.
[46:34] Let's go ahead and rewrite some of this stuff. Save class name. Is this string here, and isPending...wait, what is it? Yeah, isPending, I don't know how to spell. Then we'll do Pokémon loading, otherwise we'll do an empty string.
[46:56] Now we click on Pikachu, click on Charizard, and click on Mew. For users where it's not fast, it takes 450 milliseconds -- or maybe it takes 5,000 milliseconds -- for those users, then they will see the loading state appear at a reasonable time. I messed it up. This is what the React team is telling people to do right now.
[47:31] We can take things even further, because this is still not awesome. Let's say that this takes 350 milliseconds to load. Now we get in here. You see a little flash of that loading indicator. Let's do 400 milliseconds so it's more obvious. You see a tiny little flash. We're basically back to the same problem that we were at before. We just moved to the target a little bit.
[47:57] Now we are the point where we have to decide between which users are going to have a good experience, and which are going to have that flash of loading state.
[48:06] React currently has in its experimental Node, a way to deal with this problem. It's a way to say, "Hey React, I don't want you to show this loading indicator for 300 milliseconds, but if it does show, then I want you to stay with the loading indicator for at least a total of 500 milliseconds," or something like that, so that you don't get that flash.
[48:36] I remember building something like this with AngularJS, and it was not fun. This is way easier, even though the API is a little awkward. To be perfectly frank with you, I'm struggling still to make it behave the way that I want it to. I think it's buggy, because of course, I can't be wrong. No, I'm kidding. [laughs] I could also be wrong.
[49:00] I'm pretty sure it's buggy, because I talked with Dan about it, and it's not working the way he told me it is. The API for this is busy delay milliseconds and busy minDuration milliseconds. If we go down here and add that to our Suspense config, we're going to say, "The busy delay is going to be the same amount of time that our Pokémon loading transition delay is."
[49:23] This doesn't delay when React sets the isPending state. This is a way to say, "Hey, if it takes this long, then I want you to keep it around for the length of time that I'm going to specify."
[49:39] If it takes 300 milliseconds, then we want it to stay around for at least a total of 500 milliseconds, that's what we're going to say. That means that it will, because of our CSS transition, it's going to show for 200 milliseconds. I think that's the way that it works.
[50:01] The way that Dan is using it in a demo project, this is what you would say, but that doesn't work for me. Like I said, I think it's buggy. With this in place now, if I come in here, I say Pikachu and Charizard, see, it's not quite working. Sometimes it works, but sometimes it doesn't work quite right. I'm not exactly sure where that is.
[50:26] The takeaway here is, even though it's not quite working the way that we'd expect it to, the takeaway is that, there will be a mechanism for you to manage this kind of a problem, where you have a flash of loading state.
[50:42] Whether it's this API, it's unlikely. Dan said he doesn't like the API, they may change it. There will be an API, probably in the Suspense config that you pass to use transition, to control this type of experience. That's that. Any questions about this stuff?
Audience Member: [51:01] You said trying to summarize this very simplistically, so useTransition is going to wrap around something that is an async process, and then give us a loading indicator? Is that all it's doing, or is there more to it?
Instructor: [51:16] Specifically, it's a way to say like...taking a sit back, we want to give the user some indication that work is happening, and we want to tell React to not show the fallback or we want to control how soon React shows the fallback.
[51:34] Actually, they used to have an API when it was like last year, when they were still working on things, where you put it on the suspense component, it'd be next duration, then it's at 3,000. After 3,000 milliseconds, then they'll show the fallback.
[51:50] They decided against that number [inaudible] because this is a lot better. Now, you can control it at the place where the transition was started. You say, "Hey, any component that's suspended because of what happens is this callback, I want them to not show the fallback for 4,000 milliseconds. During that time, I want to know that that's happening, so that I can show some sort of indication." That's the idea.
Audience Member: [52:17] Thank you.
Audience Member: [52:17] What if you have...Sorry, go ahead.
Audience Member: [52:21] I was trying to say, in the useEffect, you'd have maintained three different states. Each one of them would have said, "setLoading is loading." You'd have other states to do that, and then have Effects to handle that. Instead of that, you have a simple load to do.
Instructor: [52:35] If you wanted to do the same thing with useEffect and regular React, it would take a fair amount more of marshaling of state around. Shaun, did you have something?
Audience Member: [52:49] If you have multiple suspending components underneath, or elements underneath your Suspense, and they're all using different transition configs, what happens there?
Instructor: [53:09] If we had some other suspending thing, and then here we had some other resource and then whatever, so if this one triggers Suspense, but this one does not, then what you're going to wind up with is...
[53:28] Actually, when Suspense happens, it's not the component that does this suspending, or that triggers the suspending, that's going to be suspended, it's going to be everything under the closest Suspense boundary. Everything.
[53:46] Even if you have something that does not suspend, and that starts experiencing some state updates or whatever, that will also be suspended, because it's all included under this Suspense component here.
[54:05] If you had multiple different kinds of transitions and things, it wouldn't really make too much of a difference. It's going to suspend both of them until it's ready to go.
Audience Member: [54:18] You see routes being collated to a lot of Suspense boundaries at every single route, based on what it's implemented at or...?
Instructor: [54:31] It's hard for me to tell you what people are going to do, when we can actually use this in a real app, with regard to router, because I think it'll be maybe a little bit different. I'm going to assume that you're going to have a single Suspense boundary for the whole router. That'll just be like a giant loading spinner or something.
[54:56] Then you'll have a Suspense boundary specific for the components that have fallbacks, that are better for the particular thing that you're suspending. In our case, our fallback is pretty good, because it resembles what things are going to look like when they finish loading. It's a better user experience for that reason.
[55:20] We can see, "Oh, this is what it's gonna be." It's the Facebook thing, where they show a fake version of what you're about to look at. I see people putting Suspense boundaries anywhere that they want to have those kinds of experiences. I'm super ready to...Oh.
[55:45] [distorted audio]
Instructor: [55:46] Hey George. Sorry, hold on, hold on one second. George, [laughs] George, wait. Hold on. [laughs] George. I'm going to interrupt you, George. There's something wrong with your microphone. You sound like a chipmunk.
[56:05] I'm not sure what it is. I can't understand you, sorry. Can you try again? I don't know what's wrong with your microphone. [laughs] I can't understand your question. Yeah, type it out or something. [laughs] Sorry.
[56:24] [pause]
Instructor: [56:34] Possibly. While George is typing that out, I'm done for this exercise, outside of that last question. Come down here to the bottom of the exercise, and copy this URL. Write down the things that you learned.
[56:52] Then, we're going to take our lunch break, or dinner break, or whatever time...It's almost 2:00 for me -- it's 20 till 2:00. We'll come back in a little less than 30 minutes, so 10 after the hour. It'll be great. George, I'll hang out for a minute to answer whatever question you've got.
Audience Member: [57:19] Just quickly, when does this workshop finish?
Instructor: [57:23] Somebody mentioned in the chat earlier, that there was a discrepancy between the way this was advertised in the calendar event. Sorry about that. It is a five-hour workshop. We've got...Let's see, how long have we been here? We started at...I can't even remember. We've got till 4 o'clock, my time, so a little over two hours. After we get back, we'll have a little under two hours left.
[57:58] George, any questions?
Audience Member: [57:59] Thanks.
Instructor: [57:59] Sure thing. You want, suspending component for each...whether you should have one Suspense component for each component that suspends? No, not necessarily. It depends on where do you want to have your fallbacks.
[58:24] There could be multiple components that are using the same resource. All of them are going to suspend, because they're using the same resource, but none of them can be displayed until that resource resolves. You could have one Suspense component for all of those.
[58:41] This is one of those things that you're going to have to play around with, for each individual experience. I wouldn't make a hard and fast rule that says that any suspending component must be surrounded by a Suspense component. That would probably be ill-advised.
[58:55] I'm going to go to lunch. I will see you at 10 after the hour. See you soon.