With the release of React 18 we finally get the long awaited concurrent rendering. But how is that going to affect your application? What are the benefits of concurrent rendering in React? What do you need to do to switch to concurrent rendering when you upgrade to React 18? And what if you don’t want or can’t use concurrent rendering yet?
There are some behavior changes you need to be aware of! In this workshop we will cover all of those subjects and more.
Join me with your laptop in this interactive workshop. You will see how easy it is to switch to concurrent rendering in your React application. You will learn all about concurrent rendering, SuspenseList, the startTransition API and more.
Concurrent Rendering Adventures in React 18
- Intro2 minutes
- Workshop goal2 minutes
- Type it out by hand?less than a minute
- Prerequisites6 minutes
- Following Along2 minutes
- React 17half a minute
- "<Suspence />"13 minutes
- <Suspence /> & Errors12 minutes
- Nesting9 minutes
- Parallel25 minutes
- New hooks16 minutes
- Concurrent Mode18 minutes
- Using use Transition()9 minutes
- UI state transitions with use Transitions()12 minutes
- Conclusion4 minutes
With the release of React 18 we finally get the long awaited concurrent rendering. But how is that going to affect your application? What are the benefits of concurrent rendering in React? What do you need to do to switch to concurrent rendering when you upgrade to React 18? And what if you don’t want or can’t use concurrent rendering yet?
AI Generated Video Summary
The workshop covers the new features of React 17 and React 18, including suspense, error handling, and concurrent mode. It emphasizes the importance of hands-on learning and typing code instead of just copying. The workshop explores using suspense and error boundaries to handle errors and improve the user experience. It also introduces new features in React 18, such as start transition and useTransition, to enhance responsiveness. The interaction between suspense and start transition is discussed, highlighting their collaboration in handling fallback UI and communication.
1. Introduction to Concurrent Rendering
Welcome to the workshop on concurrent rendering adventures in React18. I'm Maurice de Beer, a freelance developer instructor and Microsoft MVP. Follow me on Twitter and check out my website for more information. Subscribe to my weekly React newsletter for updates. You'll receive a copy of the slides as well.
Okay, so it's time to get started. Welcome everyone to this workshop on concurrent rendering adventures in React18. Helps if the right window has focus. Who am I? My name is Maurice de Beer, also known as the problem solver. I'm a Microsoft MVP amongst other things, which doesn't mean I work for Microsoft. But I sort of do their marketing for free occasionally, which I guess gives me the bad end of the deal, but they do give me a bunch of free software and other things. Also a freelance developer instructor. I believe in combining the two, because if I develop, then when I teach, I can tell people what really works. When I teach, I have to keep thinking about new things, other ways, better ways of doing things, which keeps me sharp as a developer, so it kind of works well together. Also on Twitter, if you want to follow me, my website is here and you can scan the QR code, you'll get to my website as well and my email address. I also publish a weekly newsletter, React newsletter. We're up to issue something like 310 or something, so I've been doing that for a while. You can scan the QR code here and it will take you to a registration form where you can put in your name and email address. You'll get one newsletter a week, every Wednesday. So the one for today actually went out about an hour ago, but next week another one will go out. I'm not going to use your email for anything else, so don't worry about getting spammed and things like that, or me selling your email address to some other companies. There are huge lists of email addresses leaked by the big companies, so my small list isn't going to be very influential there. And if you don't like the newsletter, you can unsubscribe at any point in time. You will get a copy of these slides, so if you didn't get a chance to scan the QR code or that, you'll get it anyway.
2. Exploring React 17 and React 18 Features
We'll explore the new features of React 17 and React 18, including suspense, handling errors, rendering existing applications, suspense list, transitions, and concurrent mode. Please note that we won't cover all the features of React 18, such as server-side rendering with suspense.
So what's the workshop goal? We're going to take a look at what's new with concurrent rendering, suspense, in some degree what's not new. We're going to start off with some stuff in React 17 because what we can do with suspense now is kind of important. So we're going to take a look at how to use suspense, how to paralyze nest suspense, how to handle errors that occur in a suspense boundary. Then we're going to switch to React 18 which is still in preview at the moment, not released yet. We'll see how we can render existing React application in React 18 using CreateRoute. We'll see new capabilities of suspense with things like suspense list and transitions. We'll see what concurrent mode does, how it could potentially influence the performance of your application quite a bit and how you would do that. We'll take a look at some other bits. We're not going to look at everything React 18 has to offer. There is far more. There was a lot of server-side rendering stuff there like currently with React 17 you can't do server-side rendering with suspense. With React 18, if you used to create route API, you can, but that's not something we're going to cover.
3. Advice on Learning and Typing
Your memory works better if you do things. Try and do the exercises instead of just watching and copying code. Making typos and recognizing errors will help you learn. Copying and pasting isn't enough in real application development. Typing things out is beneficial for learning.
A bit of advice. Your memory works better if you do things. If you just watch the presentation and copy and paste all the code from the exercises, that's fine if you want to do that, but you won't remember nearly as much as if you actually try and do it. If you do it, you'll make typos. I make plenty of typos. Those typos will result in errors and you'll recognize those errors when they happen again. Because when you really build applications, you can't just copy and paste. It would be very nice if you could copy and paste your complete application. But then again, if you can, anyone can, and why do we get paid good money for building applications if all you need to do is be able to do copy and paste. So help your brain cells and type things out.
4. Prerequisites and Setup
To start, ensure you have Node and npm set up. Check the versions using 'node -v' and 'npm -v'. Clone the starter repository from GitHub and install the npm packages. Open Visual Studio Code and run 'npm start'. The application displays a list of users with details and a list of prime numbers. The rendering can be slow, especially with larger numbers. The workshop includes interactive slides with code examples and links to the GitHub repository.
There are also some prerequisites. Now, this is not a beginner React course, so I'm going to assume that everyone has node and npm set up, but just in case, we're going to check. And then we're going to start with the starter GitHub repository which is going to be the basis of all the stuff we're going to work with. Which I've got prepared, and I'll show you in a second how to set that up. But first, prerequisites.
If you open up a terminal window, and you do notes-version, or notes-v, it will tell you the version of node, and that should be something like node 14, or later. I'm not 100% sure what the latest version is. In fact, I can check by just clicking on that. And of course, that opens on my other window. So the current version is actually 16.13. You don't need to be on the very latest. If you're somewhere on node 14 or later, that's fine. And then presumably 12, or later is fine. The same with NPM, if you do NPM –version, or NPM –v, it will tell you the version of NPM. I'm on 7.24. I think the latest is already at eight something, but anything with version six or later should be fine.
Then we need the repository, the starter repository. So I've got that on GitHub. Copy that link in a moment to the chat window. But that will take you here to GitHub. You can clone this repository. Copy that, go to the terminal and do a git clone with that URL and that should clone the repository. It's not very large, so that shouldn't take long. Cd into it and we can do an npm install, npm i or npm ci if you want to, whatever you prefer and it will start installing all the npm packages.
So let me copy this URL for the GitHub repository into the chat window on Discord. And just in case people didn't get to Discord in zoom as well. So that's installed let me open up Visual Studio codes before I actually start the application and show you what it does. So it's created with create react app. So usual npm start to start it up. And that's very special there. It starts and keeps on opening up. On the other window, that's okay. There it is. It's a simple application, we've got a list of users here. If I click on the user, we see some user details. And we see some details about his favorite movie. Same here. Notice when I load the details, two spinners appear. And the favorite movie always finishes first. So that resolves first, and the user details always resolve last. That's actually artificially done, because I've added a bit of weight there. But that is something which we'll come back to a couple of times during the workshop. There's also a list of prime numbers, because every good business application needs a list of prime numbers. Well, actually maybe not, but it's kind of useful here, not because they're prime numbers, but because it's expensive to render a list of prime numbers. And expansive, slow rendering is something which comes up quite a lot with bigger applications. So this is kind of artificial, but it does serve a purpose. Now if I take the slider, you can see I can move it around and it recalculates that list of numbers. Kind of responsive here. But if I go to larger numbers, then all of a sudden it's not that responsive anymore. So I'm dragging my mouse over the slider and you can see that it lags behind. And even if I just click like I'm clicking now, you can see it takes a bit of time, and if I go to the very large numbers, up to a million, if I click you can see it's not very responsive at all, which is something we can fix with concurrent rendering. Back to the slides. So that's the repository clones. That's the NPM install I just did.
Now, there's lots of interactive bits in this workflow, and pretty much every time I'm going to write code, there is going to be, or not pretty much, every time I'm going to write code, there is going to be a slide like this, and these slides are actually links. If you click on them, you get to the GitHub repository and you get to the actual commit, which contains that change. So here on the slide you could see something about suspense, and movie details, and stuff like that. And you can see here with the green, I added the suspense boundary around the existing code. Of course you can go and copy this. It's code, after all. But like I mentioned before, it's better to use this as an example of what to do than actually copying it. But, of course, in the end, that's up to you. I also got a link to the repository here, also a link to the slide deck. And let me copy and paste this into the Slack window, or in the Discord window, and in the Zoom chat as well. Because that makes it a lot easier to click on those images. Like I can scroll down here and... where was that first one here? Click on it and... This page is taking too long to load, that's nice. GitHub is playing up.
5. Breakout Rooms and Task Execution
GitHub is playing up. We're going to use the Zoom breakout rooms. I'll open them whenever it's time to do something. Most tasks are relatively short, so I'll open them in like five minutes. If you're done with whatever needs to be done, feel free to leave the breakout room, come back to the main room.
GitHub is playing up. There it is. I refreshed and it did show up. Another thing you'll see is Captain Jean-Luc Picard with his famous Make It So. That's basically your cue to start doing something. We're going to use the Zoom breakout rooms. I'll open them whenever it's time to do something. Depending on what the actual task is, I'll do it somewhat slightly longer or shorter. Most tasks are relatively short, so I'll open them in like five minutes. If you're done with whatever needs to be done, feel free to leave the breakout room, come back to the main room. If I see that everyone's left the breakout room, there's no need to wait. We can just continue with the next step. If not, I'm going to wait for that timeout.
6. Understanding React 17 and Suspense
We start with React 17 and use suspense to suspend rendering of components. Suspense is triggered by throwing a promise, typically an AJAX request, inside the render. When the promise resolves, the component is re-rendered. Errors are handled by error boundaries. The SWR library can be configured to use suspense. Adding a suspense boundary and fallback mechanism fixes broken applications. TypeScript is used in this workshop.
So, let's continue. Like I mentioned in the intro, the first part, we're actually going to start with React 17 and how we can use suspense inside of React 17 and what benefit it brings to, and then we'll build on top of that when we get to React 18. Now, let's have a look what's going on.
And that something from a server typically is one of two things. It's either you're doing an AJAX request where you're fetching data you want to render. Or the other is you are lazily loading components and you're doing an AJAX request to lazily load some codes so you can render that component. But it hasn't been loaded yet, so it isn't available yet. And then when that promise is thrown, React basically says, okay, we're going to suspend this component or that component subtree, and we're going to wait for that promise to be done. And a promise can be done in one of two ways. It can either just reject, in which case there's an error, or it can resolve, in which case there is success. And if the component subtree is suspended and the promise that suspended it resolves, then react says okay, we're going to render that subtree again and presumably whatever cause it to suspend is done now, so it will render and produce whatever, well, markup DOM elements we want. Potentially it could suspend again to fetch some more data. And in case of an error, React is going to say okay, in that case there is an error, we're going to call into an error boundary or we're going to search for an error boundary and we're going to let it do its thing and if there is no error boundary, we're actually going to kill the application.
Now the application I'm using fetches data. And if we go to source components, and for instance, in users, there is this account details. We can see here that it uses the use SWR hook. So that's a data fetching hook from Vercel called Stale While Revalidate. So it fetches data and refetches data, et cetera. And that's an AJAX request. We've got the traditional code here, handle errors, if there was some error, if we don't have any data yet, we'll show some kind of loading indicator that we're loading and that's actually what shows that spinner. When I refresh, it already had some data in cache. The spinner that shows up here, that's the loading. And if the data is there, then neither of these is gonna render and it's actually gonna go right here. And of course if there is an error, let me just quickly introduce an error by making that URL invalid. So now if I click on the user, we see a spinner and then we see not found because that URL was actually invalid. But let's make it valid again. Well turns out SWR, data fetching library doesn't work with suspense out of the box, but it's really easy to make it do so. In the index.js there is this SWR config, and it has react-context under the hood. And we provide some context on how SWR should work. And it has an option there, suspense, which defaults to false, but we can set that to true. And now it will start using suspense automatically, unless overridden by some other, we could overhead this on an individual case, but we're not doing so. So all of a sudden, all our Ajax requests have switched to suspense. Now this is specific to the way SWR works, but if you're using react query, for instance, it has pretty much the same setup and lots of React libraries for fetching data will do the same. If you're using React lazy to lazy load components, it will automatically loop into suspense. You can't even turn it off, it will always do so. However, now my application is broken. If I make sure that my caches are clear by pressing a 5, I go to users, we get an error. Oops, something went wrong. User list, that was that list of users which showed up before, is being fetched now, but it started using suspense. And that means that we need to have this suspense fallback mechanism. So somewhere I need to add a suspense boundary. And I typically add them at multiple places. But I'll start at the very root. And we'll add some more later on. And we want some fallback so I've got a loading component. And that doesn't quite resolve yet until I've got the insuspense. Now that should resolve the import. So I've got a suspense object. And now if I go back to the root, and now do the same concurrent rendering adventures, we see that it actually fetches its data and it uses suspense there. And if I click on the user, it loads the data. Now you might've noticed there is behavior difference cause originally when I clicked on the user, we would see two spinners here, one for the user details and one for the favorite users. Now, all of a sudden, the whole UI is replaced by one spinner, so not very nice, I would say. We'll actually fix that in a minute because we can do quite a lot with suspense. But if we look at that account details now, it's like, well, are we gonna get errors? No, because with suspense, errors are handled in a different way, so we can get rid of that error. If we don't have data yet, do we need to show a loading indicator? No, because with suspense, that whole mechanism of that throwing a promise kicks in. So we should never actually get here without any data, so we should be able to get rid of that. Now I'm using TypeScript, and I hope you're all TypeScript fans. I am a TypeScript fan and this series React work or front-end work I do with TypeScript or quite a bit of backend work as well. So it actually complains here saying, well, account is an account or undefined. That's because the API here under the hood is using suspense so we kind of know that this account really is an account or a promise is gonna be thrown.
7. Using Suspense and Error Boundaries
In this section, we learned about using suspense in React 17. We used TypeScript to handle potential undefined values and eliminated unnecessary code. We also explored the SWR library for data fetching and added suspense boundaries to our application. The loading behavior can be improved, which we'll address later.
It's never gonna be undefined. If something went wrong, we won't get here, we'll get to an error boundary, except that the typing can't really know that we're using suspense, so the typing actually assumes, well, the account could be undefined. So a little trick with TypeScript is you can't change that inline to be not undefined. Now, I've got exactly the same thing, but now I can put an exclamation mark behind this saying, well, data is maybe an account or undefined but I know it's never undefined so it's always an object. So if I check the account type it's always an account not undefined, and my compile errors go away. And I've deleted quite a bit of code. I can do the same with the movie details, that error and that's not loaded part goes away. Don't need that error anymore. And this movie is always an object. So same thing with the exclamation mark. In the user list, we've got similar code, lots of similar code there. I didn't actually rename the data. I'm just using data here. And now we've got a compile error right here saying object possibly undefined. So again, an exclamation mark or I could potentially put a question mark or I could use another way in TypeScript to do it, the null-coalescence operator. Either will work. So got rid of a lot of extra stuff there which we didn't need and I'm actually got some inputs left here which I can get rid of as well. Where was the other one? There, that's the one I was looking for. Okay. So, make sure everything works. I can load the users, the data shows up. Loading behavior isn't quite perfect like I mentioned but we'll fix that later. So that's the basics of suspense. Remember that fallback component or fallback prop you have to specify to suspense. It can be a component, it could just be a string. Works really well. There is a slight behavior change with suspense in React 18 with that fallback prop but I'll talk more about that when we get to React 18. So there is SWR which we're using here to fetch data. Pretty nice and convenient utility to use for that. The change I made there, adding suspenses through to the SWR config and adding the suspense boundary right at the root of our application. Kind of a last resort to catch all the suspenses. The update to user lists, account details, and movie details, where there isn't an arrow because I all just deleted code. Well, pretty much deleted code. So there wasn't much to show Which works really well. And of course, the results. This is when it's actually loading, which isn't quite as nice, but we'll fix that in a minute.
8. Exploring Error Handling and Suspense Boundaries
Let's open the breakout rooms again for a longer duration. I'll provide the repository link to Ravi. Then, we'll explore error handling, orchestrating suspense boundaries, and the resulting outcomes.
So let's go and do this. I'm gonna open up the breakout rooms again. I'm gonna do that for slightly longer. Where did my breakout rooms go? The window went away. There it is again. Okay. Ravi is asking for the repository. So let me actually copy that into the chat window for him. And then I'm gonna open up the breakout rooms for eight minutes. So you can do this step and get everything to work with suspense. And after that, we'll start looking at how we can work with errors, catch errors and then with different ways of orchestrating different suspense boundaries together and the results we'll get.
9. Error Boundaries and Handling Errors
In this section, we learned about error boundaries in React and how they handle errors in components. We saw that without an error boundary, React unmounts the whole application when an error occurs, resulting in a blank page for the end user. However, by adding error boundaries, we can catch errors in specific components and have the rest of the application continue rendering. We also explored nesting error boundaries and the benefits of doing so. I recommend using the standard React error boundary npm package, which allows for error retries. Please add error boundaries to your application. Next, we'll move on to nesting suspense components.
Okay, so suspense. Suspense boundary's relatively easy to add, but what happens if there are errors? Well, if there are errors in a normal React component when rendering, we have an error boundary. And in suspense, that's really no different. So I don't have to add any error code to the individual components doing things. I just add error boundaries just like I have suspense boundaries. And I actually had one by default. If I go back to my index.tsx, right, oh, that's not readable. Right here at the root of our application, I had this error boundary with a fallback component which actually displays it. And I don't have any code in here to do so but normally I would also have code in here that would make sure that error would be sent to a server, collected there, and something like Sentry, et cetera, would be used to collect all errors. So I could go and find the bugs and fix them. And we can actually see that if I introduce an error, let me make this URL invalid again and go back here and make sure there was no data in the cache. I go to the users, I click on the first user, we see loading. We see this error screen, which is the result of being in the development environment. But at runtime, we would see this, well, something went wrong, status text was not found, because it's a 404, not found. Now, this is only there because of the error boundary. Let's remove that error boundary for a second and see what happens in a normal application without an error boundary. And I'm sure you've all seen that before. We'll refresh the application, click on the user. We'll get that same 404. Because of development environment, we get this again, but at the runtime, the end user would get to see this, a complete blank page. Not very informative, not very useful, but because there is no error boundary, React basically unmounts the whole application. If I go to the console in the Developer Tools, I can actually see that stuff went wrong. And down here I can see the complete components stack that was unmounted because there was nothing handling this. There was no error boundary handling this. Exactly the same as what would happen with other errors. So, let me undo this so that error boundary comes back. And now, it should be able to catch it again. Let's make sure, yeah. Now, still this isn't very nice, just because one component blew up. It's kind of, it still unmounts the whole application because I've only got one error boundary. And that's right at the root of our application. In reality, it's only one component which errored out. Well, the nice thing with error boundaries, I can test them. So, I can take this error boundary and go into that user details component, which is responsible for the account details and the favorite movie details. And I could say, well, I want an error boundary in here. And I'll do the same around movie details. Resolve these imports so it actually compiles again. And now if I refresh, so I've got my application again, I click on the user. We get to development error page. But now I actually see that, okay, just the user details component errored and the error boundary around that caught it. But all the others still rendered, and I can go to another component or another user and it will still render. Of course, that user details component isn't gonna fix itself, the URL is invalid, so it's never gonna go there, but potentially I can close that error boundary and have it retry. It's not gonna fix itself, of course, but with some other error, it might actually. It might be because of the network was unavailable. And now, if I close it, it actually comes back and is able to fetch the data. So nesting these error boundaries is really nice and really useful. So that's error boundary at the root, which is already there, so no need to add that. But introduce that same error I just did by making that URL invalid. So you get a 404 not found when trying to fetch a specific user. And then, add that error boundary inside of the UserDetails component. And right here, you also see a Suspense component. No need to add that. That's actually a slight error in my screenshot that's from a later stage when there was both a suspense error and an error boundary. Just add the error boundaries around the AccountDetails and around the MovieDetails there. So these two. And then, you should be able to catch the error around just that single component and have all the others render. And I'm actually using the standard React error boundary npm package here, which is really nice, which actually lets it retry. So if you click on that cross, it will actually retry. I didn't create a custom error boundary or anything. This is a real nice package and highly recommended to use in your application. So please go and add these. I'm going to open up the breakout rooms for 5 minutes again. After that, we're going to take a look at nesting suspense components, just like we can nest error boundaries. Because in a lot of respects, they work in very similar ways. But that's the next step. In this step, let's create that nested error boundary and check what happens when errors occur. So everyone back. Everyone successful with this step? The error boundary catch errors. Okay. Thumbs up.
10. Nesting Error Boundaries and Suspense Boundaries
The suspense component catches promises that are thrown, while the error boundary catches errors. React starts at the component that triggers suspense and walks up the tree to find the first error boundary. The difference between the two is that the error boundary has a fallback property that can potentially suspend, while the suspense component is inside the error boundary. It's recommended to have an error boundary at the root of the application to handle all errors. Nesting error boundaries and suspense boundaries is useful. Putting a suspense boundary around the app and a suspense boundary around the specific component can improve the user experience. However, it's important to consider the impact on the application's performance. Keeping a list of users and showing a spinner for user details can be achieved by nesting suspense components.
So looking good. So there are actually two questions. One from Alexi. I thought that suspense component is catching errors that are thrown by hooks or other child components. How would error boundary component catch the error if suspense is its child? Suspense component don't actually catch errors. They catch promises that are thrown. And if a promise is thrown, then the suspense boundary kicks in, even other thing is thrown an error or something else, a string or a number, but you should always throw error objects, then the suspense of a boundary is going to ignore it. And it's going to leave it up to error boundary to handle that. So- That makes sense, the only thing like if promise is rejected, like does also like consider it as an error? Or like yes, if the promises rejected, that's an error and that will be treated the same way as if an error was thrown in the first place. So error boundary would be able to catch this probably. And in that case, React starts at the component which suspense and starts walking up to three to find the first error boundary from there. Not from the suspense component, but from the original component which started the suspense.
And there was an other question and I'll just open up to Discord here for a moment to show. The difference between the two and the first has the suspense boundary and nested inside of it is the error boundary and inside of that is the component. And with two, we've got the error boundary at the outside, suspense inside of it and the components inside of that. If there are any difference between the two, yes. But that said, in practical purposes, probably not. The only difference is, if you look at the error boundary here, it has a fallback property that could potentially suspend. Now it's pretty unlikely that that would happen, but if there is an error, it might lazily load different components to show that. I wouldn't recommend doing that because if you've got an error because of some network connectivity issue, you're going to try to load an error component which also comes load because of that same network connectivity issue. Within that case, it could suspend and it would be caught by this same suspense components. And here it's the other way around. If the error boundary has some kind of reason to suspend with its error display, and it can't because the suspend is, or the suspense component I should say is inside of it. But now in case of the fallback from the suspense, if that's an error, well, the error boundary can catch that. what I typically do is on the root of my application, I have to error boundary at the very root because I want to be notified of all errors occurring wherever they occur. And you normally shouldn't do any asynchronous work so there's at least nothing which suspends. If I send data to an error collection service, I'm not going to do so immediately there because if there is connectivity issues, it might never arrive. So I'm going to put it in some queue and eventually I'll send things and I'll store that queue in local storage or index DB so even if the user refreshes the browser, those errors don't get lost. They might eventually get lost, but not in a normal circumstance. Of course, if the user never comes online I'm not going to get it but under normal circumstances I will eventually get that error. So, Martina says, but the first example is actually what we have now in the app because we have a suspense around the app and the error boundary around error details. Not quite, because we also have an error boundary right at the index level. Where is my index.tsx? There. So here, where I render the very first thing is an error boundary which is kind of the catch all in case of an error which isn't locally handled, will be caught by that. And then inside of that, I've got the suspense boundary which also is kind of a catch all. We haven't done nested suspense yet but normally I would suspend closer to where I actually want to. And this is kind of a last resort suspense boundary to make sure that my application doesn't fail. So the error boundary is at the outside, suspense inside, and then potentially, or most likely I'll have nested versions of those two inside of other components where I can handle things locally. So I hope that clears those two up. Let's go on to the next step. Cause as I mentioned, we can nest error boundaries, but we can also nest suspense boundaries. And that's kind of useful. Cause if I go back to my application for a moment, let's actually start here, refresh to make sure I've got everything in order. Refresh to make sure I've got nothing in the cache, and let's see what happens with a really slow network. So I've set it up to simulate a slow 3G network right now, which is pretty slow. Unrealistically slow for most applications, but now if I click on users, we see the whole page is pretty much blanked. The navigation bar goes away, and we only see that spinner. I click on the user and again, the whole page is blanked, and we don't even see the navigation bar. And if we see a list of users, now if I click on the second user, that's gone, navigation bar gone. So that's okay. Suspense boundaries work. They are caught, but it's kind of dramatic, and it's kind of removing too much. So what we can do is we can say, well, let's grab this suspense boundary and let's take a look at what's inside of this app. Well, here we can see the browser router for React Router, and we can see the nav bar, so the navigation bar at the top, and then how the routes are handled. Well, in this case, they're not done with lazy, but that's quite likely to be done. So it's quite likely that those will suspend. So putting a suspense boundary around this is actually a very good place. So let's resume those imports, and let's see what the effect is. So we'll go back here. I'll simulate the 3G again. We'll go to Users, and now we still see that loading spinner, but the navigation bar stays at the top. So I click on the user, user list goes away, but the navigation bar stays. So slightly better, but not quite good. Cause now if I switch between users, I'd like to keep that list of users and just show a spinner right here for the user details. So inside of user details here, I could say, well, I really want another suspense component in here. And need to resolve these imports as well. Now all of that is inside of a suspense boundary. So now if I click on a user that actually stays. But it's only, let me switch here again. I click on the user and very briefly, we could shoot the spinner there.
11. Nesting Suspense Boundaries
We can nest suspense boundaries to achieve a decent UI. React 18 respects null as the fallback for suspense, unlike React 17. Nesting suspense components serves us well even with React 17. Let's move on to suspense in parallel, which is similar to error boundaries.
And now we get a spinner here. And we could potentially say, well, I want that first header outside of the suspense. So user details actually appears right away. So now, if I click on the user, we see user details, and it's only the details and the favorite movie part which appears when the data is loaded. So it's not exactly what it looked like when we started, but it's pretty close. And I actually think this is a pretty decent way to nest suspense boundaries and get a pretty decent UI.
So nesting suspense boundaries, you can nest them however you want to. And basically any time a component suspense, React will start walking the component tree. So it will start at the component that suspense. Let look at its parents. If that suspense components, it will use that, if not, it will go one up, et cetera. So it will locate the closest suspense component and will use that to suspend the application. If there are multiple components suspending, they will each do this little trick by themselves and multiple suspense components can be active at the same time.
Now there is a behavior change with React 18. We're still 17, so I can't actually show that yet. But with the fallback UI we specified right here, this one, I've got a component, a Loading component which shows that spinner. I could just put some text in here, basically anything which would be valid for React to render could go in there. And a string is perfectly valid. But React also says null is perfectly valid. I can have a component which in its render returns null and that means basically no need to render anything here. Well, if you do that in React 17, React 17 is gonna ignore that null suspense boundary. It will basically treat it as if it doesn't exist. And keep walking up the tree, find the next one. With React 18, it will actually respect that and say, okay, it's fine, you don't wanna render anything, then okay, we're not gonna render anything until this resolves. Pretty unlikely to affect you. I haven't seen any production code where people use null as the fallback for suspense, but it is possible and it is behavior change.
So here was the first suspense boundary I added, the first nested, and then we had this result. And then I went into the movie details component and added one more suspense boundary right there. And we got this result when we started Click On Users, which, I know UI is not always a great tool, I'm not really an expert, but I kind of like this result and it kind of serves quite well in applications. So even with React 17, nesting these suspense components serves us really well. So let's go and do this. It's another five minute task. So I'm gonna open up the breakout room in five minutes again. Before I do... There were a couple more questions. So from Raffy... So the two one is recommended, I presume you mean from right here in Slack. So that's indeed the one I recommend. And this one I already answered. Okay. So let me open up the breakout rooms and I'll see you all back here in five minutes. So that's everyone back again. Everyone successful with this step at the next suspense boundary working. I was getting an error, actually, but I don't know, like, why is that, like in a user details, user list component, I'm getting an error on the line where we are trying to iterate over data. So this line? Yes. Yes. The question mark fixed this error, but I'm not sure why we're getting undefined here because like we are using suspense them. Like this. So you're getting a compile error, not a runtime error? No, runtime error. Runtime? Yes. Runtime, that shouldn't be the case. Yeah, that was the problem. Like, yeah, putting a question mark in here means it will work, but even an exclamation mark, which is just telling the compiler, like I know data is never undefined, treat it as an object, it's okay, it's really an array, trust me. That's not a runtime check. That's really common. I probably just messed up with the suspense somewhere at the top, maybe. Yeah, I just don't know. It looks like it worked. The thing you might want to check is in here, if you've got this suspense there on the SWR config. No. That's the piece I'm missing, actually. Yes, thank you. Yes, suspense. Okay, solved. Thank you. You're welcome. So, let's do suspense in parallel because we did error boundaries in parallel and I've mentioned a couple of times that suspense and error boundaries are really similar to each other and really do the same kind of thing except one does it for a promise being thrown and another does it for an error being thrown. But other than that, they're, well, not exactly the same obviously, but very close in the way you work with them. And we had to, where was my code? And that's user details. We've got error boundaries here in parallel, but we've only got a single suspense.
Parallel Suspense and Handling Questions
We can add suspense in parallel by duplicating suspense components for different details. However, the UI may not be perfect, and React 17 doesn't provide much control over independent suspensions. With React 18, we'll have suspense list components that coordinate multiple suspense boundaries and allow more control over rendering and resolution. After the break, a question was asked about making an existing network layer compatible with suspense, and a demonstration of throwing promises in custom code was shown.
Well, why can't we add that in parallel? Well, we can. I can just duplicate these. So I've got the suspense around account details and I've got a different suspense around movie details. And now if I go back to the application, make sure nothing is good, we get two spinners again if I click on the user. One for the movie, his favorite movie, one for his user details. They each suspend independently of each other and they each resume independently of each other. And just like before, if I do the suspense inside or outside the error boundary, let me change one of them, it doesn't matter. It's just a matter of, okay, if this fallback throws an error, then this error boundary can catch it. And in here, if this fallback component suspends, then this suspend can catch it. But like I mentioned before, that's probably not the wisest of idea. Here, I really don't mind what the order is, which you nest inside the other, in the root of my application, I think the error boundary should always go outside. Here, it's not that important. And they're still gonna work exactly the same way. Now this works, and it's really nice, but one thing I personally don't like about the UI, but then again, I'm not a UI expert. As I said before, it's the Favorite Movie Results first. So, we see two spinners. Then we see User Detail Spin, our favorite movie with the actual details. And then User Detail Resolves its data. So, the favorite movie is actually pushed down. I don't really like that UI-wise. That I can do it, that I've got the capability to organize it that way, is really nice. That the UI works that way, meh, I'm kind of not so happy about it. But in this case, I actually did that intentionally. You might have noticed, but if we go into these components. On the URL, I've got a service here where I can specify a specific sleep. So, every account detail is gonna wait one second before it responds. And every movie detail here is gonna wait half a second before it's response. So, it's always gonna response faster. I did that intentionally to show exactly this behavior of the two users and the data being pushed down. In the real application, you don't know what the order is. And sometimes, the first will resolve, sometimes a second. On a development machine, they're typically gonna response pretty fast because you typically run things locally and everything is fast in production. It goes over the internet or maybe some company network, but things are probably gonna be slower and not quite as well. With React 17, there isn't really much we can do about it. Both these suspense components are gonna be independent of each other. They're gonna do their own suspense. They're gonna do their own resumptions, and that's it. We don't get more control. The only thing we can do is say, well, I don't want this behavior. Then, put one suspense boundary around both components that suspense. When we get to React 18, we'll see the suspense list components, and the suspense list component will actually coordinate multiple suspense boundaries like this, and we do get to control how they will react or how they will render together and when they will resolve, which is really nice. But with React 17, we don't exactly have that yet. So, the parallel suspense. The change I made, in this case, I've only got to suspense, but the error boundaries can stay in there like I just had in my components. Basically, get them to suspend in parallel, and we have this result, which does work pretty nice, except in this case, the way the UI shifts isn't exactly perfect. But we'll fix that when we install React 18.
Hello, everyone back from the break. Time to continue. Before I continue, Alexei asked an interesting question in the chat window on Zoom. We're using the use SWR hook to provide a suspense compatible fetch API for our components, but what if we already had a network layer in our existing app? How could we make it compatible with suspense? And will we be going through that? Well I didn't plan to, but I can briefly sort of do that. I'm not going to create a complete library. And the second part I'm just curious about low level APIs of the suspense feature, how you can throw promise in your own code. And I can actually show that relatively easy. Where is my application right here? So let's actually just see what happens if I throw suspense. So suppose I'm in the user details here and let's add a button to the top. Suspense me or something like that. And we have an on click. Oops. And now we need to do something here. So we'll have an event handler and I'll keep it simple inline. Now, this needs to render or this needs to suspend when we render. So I can't really do it here in this on click directly because then we're in an event handler and not actually rendering. So I need to introduce a bit of state. So we'll do const suspend me. Type is hard. New states and we'll start off with false. And then here we said suspend me to true. Now, of course that doesn't actually do anything yet. So if I open up a user, we've got suspend me and I can click on it, nothing happens. Well, some state changes, but we're not using that state, but now I can use this state.
Suspend and React 18 Installation
We explore how to suspend a component using a new promise. By throwing a new promise, we can suspend the component. Resolving the promise after a timeout resolves the suspension. This technique can be used in any function called from the render cycle. Moving the code outside the render cycle, such as in an event handler, will not cause suspension. We then switch to React 18 and discuss the installation process. The latest version of React 18 can be installed using npm, and there are different tags available, including 'latest' and 'next' for the alpha version.
And in here I could see something like if suspend me, throw a new promise. And that will actually make this component suspend. And that complains because it needs a callback function. and we'll leave it empty for now. So now if I open up user and I click suspend me, we've actually suspended our component.
Now this promise never resolves or rejects. So this will stay suspended forever. So let's actually resolve it. So the promise constructor takes a function which gets a resolve function and reject, but I'm just going to resolve it for now. So we'll do a setTimeouts and we'll call that resolve after say two seconds. So now if I click on the suspend button, I actually need to set the suspend flag to false, otherwise it will re-render and immediately suspend again. So we'll set suspendFlag back to false and re-render. And actually, do I want to do that in here? I think this should be fine. Let's check. So we've got useDetailsLoaded, it's suspended and after two seconds, I was hoping it would resolve, but apparently it doesn't. So why is that? Maybe I need to do this first. Let's check. Shouldn't you invoke to resolve? I am actually doing that. I can do it explicitly, but I should be doing that by passing the function reference to the setTimeout. That should be the same, but let's check. Maybe that is actually the difference. Should we set this inside this callback? Set the suspendMe inside the callback? So after a timeout, like after 2000 milliseconds, we should set suspendMe to false. In here, you mean? Yeah, yeah, yeah inside that callback. That sounds reasonable. Let's see if that fixes it. So it suspends. That was it. Thank you. So now it suspends. It waits two seconds, and then it resolves. So the core of suspense is really very simple. It's just this thing, throw a new promise. And with that, well, that's not react-specific. You could do this anywhere. You could do this in the hook. You could do this in the library. I can put this in whatever function I want. It's just a matter of it has to be called from this render. If I move all of this code into that click handler, get rid of this for a second. Now it's not going to do anything. In fact, that doesn't even work. Why does... I've got a bracket too many, I think. No? You were having a bracket too few. Oh, right. Thank you. So now it's not going to do anything. See? No suspension, nothing. Because it's not done in the rendering. It's in an event handler. So if I move this back, just copy everything, and set the suspend me again, then it should be fine. Suspend for two seconds, and it comes back. So with that, it's really not all that hard to build into existing libraries. You just have to make sure you're inside the render cycle, and with a hook, that's easy. We'll leave that in.
Where are my slides? There. So with that out of the way, we're going to switch to React 18, and we're going to take a look at what React 18 brings to us, both with these suspense boundaries, but also with that prime numbers component, which is really slow and sluggish. But of course, the first thing we have to do is we have to install React 18, and if we look on npm, This is the React package, but with React DOM, I would see exactly the same thing. I can go to versions. And here, you'll see that different versions. Now we've got here a tag latest. That's the normal version you use when you do npm install of React. There is also this version with the tag next, which currently points to 18.0 alpha, which from hash, and then there is this date of October 23. And it's the 27th now, so that's four days old. But it was actually released it says down here two days ago. So I think what was that last Friday or so and released on Monday. Now at the moment, there is also this alpha and experimental version. And alpha and next are actually the same thing. Experimental is different. Actually, that's also the same version, which you can tell by this hash.
Installing React 18 and Updating Code
To install React 18, use the 'next' branch with the tag next. Use 'npm install React-at-next' and 'React-dom-at-next' with the '--force' flag to install the dependencies. React Router DOM 6 is compatible with React 18 and React 17. The router API has changed, now based on hooks. Use the 'useRoutes' hook to define routes. React DOM.render is no longer supported in React 18, use React root instead. Update the code to use 'createRoot' and pass the DOM element as a parameter. TypeScript may show a compile error, but it can be fixed by using the exclamation mark to assert the type of 'getElementById'.
It still has the same hash, but it doesn't have the 18.0.0 version. So at the moment, all of these three work out to the same thing. The goal here is that next should eventually become the more stable version of React 18 release candidates, et cetera. Alpha is always going to be somewhat less stable, and Experimental is basically going to be daily builds. So if you're looking for the next version to be released, the next branch here is the one you're interested in to release with the tag next. So we can go and install that. Now, if you just do an MPM install of React-at-next and React-dom-at-next, it will actually fail because of dependencies. I'm not 100% sure which of that is the case. But with a dash dash force, it will actually work. Where's the console there? So let's clear this one. MPM install React-at-next and React-dom-at-next with dash dash force. So that should install pretty quick because those packages are actually pretty small. So those are installed. And for instance, you can see with SWR. It says it wants React 16.11 to 17. So that's one of the libraries which would actually complain and reject the install without a dash dash force. Another library which would potentially be a problem. If I go to the package of JSON, you can see what I've got. Is I've got React router DOM here. Commonly used React router. Now if you use the normal version, version five, it's not compatible with React 18. So I'm actually using the beta version of React router DOM 6, which is compatible with React 18. And as we already noticed, it's also compatible with React 17. I'm not sure what the latest, the earliest version is for React router DOM 6, but it goes to React 16 something. So relatively recent versions of React will work perfectly fine with it. Now the API has changed. It's not really part of the presentation, but still useful to see. I wanted to go here, app route. The router is now based on hooks. So there is now a use routes hook you pass in an array of the different routes and the different options. Like here I'm saying for users use the element, user list and for primes use prime numbers. And for home, just have some inline markup in here. So it's changed a bit. Reactor router dom five will not work, at least not completely with react 18. So be warned about that upgrade. But that's reactor router or sort of react and react dom installed. Let's start the application again and see if it still runs. Loading dot dot dot. And there we see parling building in London again. And we see users and all of that still works. Suspense boundary works, does this still work? Yep, that works. Prime numbers still work with somewhat larger numbers. It's still slow. So it kind of looks like our application works but it really hasn't had any meaningful, observable effect. Now this, until I go to the developer tools. If I open up the console log and let's set that to no throttling, that's the one I wanted, the console. It comes up with a message here. Warning, React DOM.render is no longer supported in React 18, use React root instead. Until you switch to the new API, your app will behave as if it's running in React 17. So even though it's not supported, it does work but we're not really using any of the React 18 capabilities. It acts as if it's React 17. Well, that's not what we want. So let's go and fix that. So we'll go back to the index.tsx. Here, we've got our React dom.render, which we shouldn't use. It says use.createRoot. And React dom.createRoot returns an object and that still has a render function. So it becomes something like this, except the DOM element we want to render into becomes parameter for createRoot. So it looks like this. The only thing is there is a compile error here. Type null not assignable to element or document or document fragment or command. The reason is createRoot if I look at the typing is typed as being one of those parameters but not null or undefined. That's not a valid option. And if I look at getElementById. It returns an HTML element or null because if you specify an ID that doesn't exist, getElementById doesn't throw an error. It just returns null. We know that this element exists so TypeScript fixed the exclamation mark saying TypeScript, I know better. I know a better type. TypeScript, I know better. I know that getElementById always returns an HTML element not a null.
React 18 Rendering and New Hooks
We're using React 18 rendering, but the application is still sluggish. React 18 introduces new hooks, including useDivertValue, useTransitionHook, useMutableSource, and useOpaqueIdentifier (or useID). useOpaqueIdentifier is useful for automatically focusing input fields associated with labels. It can be challenging to achieve this behavior when rendering on both the server and client sides, but useOpaqueIdentifier simplifies the process. Note that useOpaqueIdentifier may be renamed to useID in the current version of React 18.
And now we're happy again. And now if I go back to the application and refresh it, no more errors in the console. It renders fine. And we are using React 18 rendering and everything. That said, this is still sluggish. We are using concurrent rendering, but we really can't tell yet because we're not taking advantage of it yet, but we are using it. So here's the updates to the package.json. I've got slightly older versions here. The versions are released quite often, but that shouldn't, at least hopefully because I know there is one change to the code which I'll mention when we get there, but that shouldn't affect us, I hope. So, the addition, the change to the index.tsx to react-dom.createRoot. Now, if you're into TypeScript, you might wonder how come this actually compiles and works, because createRoot didn't exist in React 17. Well, I have this tsConfig file, which you get by default if you create React application using the //template TypeScript option, but I added one thing to it, and that's this line. The react-dom and react-typings are not included with the original MPM package, they come from DefinitelyTyped. And by default, it will look at the standard typings there, but the typings from DefinitelyTyped contain the next version. So I already told TypeScript to look at these next versions of React, so it is already aware that the new API exists. So that's why I didn't get a compile error there. That's previously added. But if you create a new application or of an existing application, you're going to upgrade to React18 prior to it being released, then these types will not be updated, then you'll need to do the same thing. But right now that's already done, so no need to worry about it. So the change there. And please go and do so. So React18 actually gives us quite a lot of new features. The new hooks, there is a bunch of server-side rendering stuff which I mentioned which we're not going to look at, but server-side rendering with suspense now is possible. But the new hooks are kind of interesting and I'm going to look at a few of them. There is a new called useDivertValue which can be useful in performance scenarios. Now we're not going to use that. It doesn't actually help that much with that prime numbers but that's the kind of case where it could potentially help. In this case, it just is too slow and using DivertValue won't fix it. But what DivertValue basically will let you do is say, there is some frequently changing value and I don't particularly care about all the changes. I just get just give me some changes and give me the final value when it settles down. If you look at the documentation for the next version of React, still kind of dated. It says there is a parameter there where you can specify how much it can lag behind that doesn't actually exist in the current code base. So documentation is slightly wrong there and there isn't really much you can configure. You push a value in, which frequently changes and you get a value out. There's a useTransitionHook which is very useful for state transitions and we'll actually use that for a couple of different scenarios. So I'll leave that for later. There is a new hook called useMutableSource which is not really meant for end developers or application developers. It's more intended for library developers like Redux, MobX, that kind of thing. And it's meant to prevent tearing of UI. Now I'll come back later and explain what the tearing of UI means and how that can happen with React 18 because that's something which previously couldn't happen. And then the reason new hook useOpaqueIdentifier or maybe it's actually called useID. I'm not quite sure yet because when I created these slides it was still called useOpaqueIdentifier but it also had an unstable prefix and they announced last week they were renaming it to useID and removing that unstable prefix. So I'm not a hundred percent sure what's in the current version of React we just installed whether it's already been renamed in there or that it hasn't yet, but that's actually the hook we're going to play around with first. And let me show you where you would want to use that. If I go back to the list of users we've got a bit of an entry form here. Now, these fields are disabled, but they can get focused. And you can see there is a focus bar around it. This is standard bootstrap styling by the way. Now, quite often in an HTML form, if you click on the label associated with an input, like I'm clicking on surname now, then the input associated with that will get focused. But that doesn't happen here. I could wire those up manually, but that's kind of tedious. And you'd kind of want to do that automatically. Now, that wasn't particularly hard to do with a custom hook in React as long as everything was client-side-rendered. But if you wanted to do that server-side-rendered and then do exactly the same thing client-side again, so it was consistent and it wouldn't have to unmount, remount components or rerender them unnecessarily, that turned out to be quite tricky. Well, that's exactly what useOpaqueIdentifier does. So if I actually go to the components in question and let's label inputs, I could say something like I'll create an id here and for now I'll just create it fixed. It's the string id. And with the label we do an HTML4 for that id. And with an equal sign there, that input has an id. So now they are associated and we should get that focus behavior. So if I click on the first name, first name get focused. Except if I click somewhere else, I click on email address it has the same id so it still sets that first name focus. So instead of this hard coded, that's where we want to use that opaque identifier. So, let's see if that still works as it did last week. So I import use of opaque identifier with the unstable prefix and I call it or no parameters or anything and then let's make sure this is refreshed. If I click on first name, first name gets focused. If I click on surname, the surname gets focused, email, title, et cetera, except with overview because that's text area but we could do exactly the same in there. So, pretty simple. But remember, this will be renamed quite possibly it would have been broken right now. I'm expecting if I do the same steps again next week, then it's pretty much guaranteed to be broken and I have to use the use ID hook instead of use opaque identifier.
Using the useOpaqueIdentifier Hook
The useOpaqueIdentifier hook in React 18 provides an opaque ID, which is a string without any meaning. It is useful for server-side rendering and recommended to use in such cases.
Which to be honest is a bit of mouthful. Use opaque identifier, whoa, why opaque? The reason it's actually called opaque is because you shouldn't put any meaning to that ID. It's actually a string but the string itself doesn't have a meaning. You shouldn't look into it. It's just an ID. Still small thing but useful especially if you do server-side rendering. If you don't do server-side rendering and you have some kind of hook to generate IDs like this, just stick with that. There is no need to immediately switch. It's not like this is gonna be better. But if you're doing server-side rendering then this is definitely the recommended hook to use there.
Using the Suspense List Component
We can use the suspense list component to control multiple suspense boundaries and customize their display, resolution, and fallback components. By specifying the reveal order as 'together', the suspense components inside the suspense list will resolve simultaneously, resulting in a better user experience. The 'forwards' option allows us to decide how to handle remaining components, with the 'hidden' value hiding them. By adding a key to the component, we ensure that it is always remounted, providing consistent behavior. The suspense list is a valuable addition to managing individual suspense boundaries and controlling their interaction.
So the small change to labeled input and the two props sets to the input and the label and the results where it gets focused. If you wanna be complete there was also, where is it? Text area components labeled text area. You could do exactly the same change in there as well. But of course, that's more of the same, so kind of optional.
Okay. So like I said, the next step we're gonna take a look at how we can orchestrate different suspense boundaries using a new components, the suspense list component. And suspense list is pretty neat. It will let you control a number of different suspense boundaries and give you a number of options about how they are displayed, how they resolve and what fallback component should be rendered or not rendered. Just like suspense, you can start nesting suspense list components, et cetera. So it's just as flexible.
Now, let's actually use it to get rid of this problem where if I click on user, we see two spinners and they kind of always resolve in the order of the bottom first and then the top one which means that the UI jumps around a bit. That favorite movie is pushed down when the user details resolve. So what we can do is in here, we could add a suspense list component. Let me put it down here. And let's add the end suspense list. and right now, that's actually not even gonna make a difference. I added it but we see exactly the same behavior as before. If you don't configure it, you don't really get anything else. There are two properties to work with. Most important is the reveal order and that can have three different values, backwards, forwards or together. I'm not sure when I would use backwards but forwards or together are actually pretty useful. So if I say that together, we get some, what I think is pretty nice behavior. I click on the user, we see two spinners but now instead of the favorite movie resolving first and the user details resolving after that pushing the favorite movie down, they actually resolve at exactly the same time. What suspense list does if you specify together, it will basically wait until all the suspense components inside of the suspense list have resolved and only when they've all resolved will they actually resolve. It's not like one will resolve before the other. So it kind of combines them, which is nice. We get two spinners at the same time. Whenever the last one is done, they both disappear and the UI renders. Which is a lot better than what it was before. Another option we can do is forwards, for words with an S. In that case, we can decide what we want to do with the remainder, there is a tail option and that takes two values, collapsed or hidden. I'll first take hidden, which is the one I not a big fan of, but it will certainly have its uses. Now, if I click on the user, you can see user details appears, but below that, nothing. Let me do that again. Nothing. But when I click on the second user, it actually behaves slightly different. Now we see loading spinners and they resolve at the same time. So, it's when the component first mounts, it behaves one way, but when it is actually refreshed and suspends while mounted, it behaves in a slightly different way. Not sure if this is a bug or not, but it's not what I would want in the UI. So, what I typically do in cases like that, I make sure that it's always mounted. And that's actually pretty simple. Here, I'm in the component which renders that list and the user detail what's selected. If you use a key there, which is normally done on lists of items, but that will work on any component. If you specify a key and that key changes, it means that the component instance, that user details will be unmounted and a new one will be mounted. So, now I've got the behavior that we get new user details components with every user. So, we get the same behavior. You see the user details appear first, movie always after that, even though favorite movie resolves faster. But no spinners, no fallback UI. The no fallback UI is because that tail is hidden. I can also do collapsed. And this will do almost the same, except it does show the first fallback behavior. So, now we see a spinner on user details, and when that resolves it showed UI, we saw the spinner on favorite movies. So, one spinner, another spinner, which is a bit weird if you consider that favorite movie is actually faster, it resolves before user. Resolves before user detail, but still that spinner shows up. But, given that it shows up from the top to the bottom, the UI is a lot easier, things don't get pushed down. It fills from the top down. You could do the same with backwards, but that's not quite as nice. Now we see a spinner, then we see favorite movies, and then we see it being pushed down with user details. I'm sure there are good cases when this would apply, but in most cases, it wouldn't. I actually think together works best. And, as you can see from the compile error, if you use together, the tail option is not supposed to be used. Because there is no tail, they work together. So two spinners resolving at exactly the same time. And that's really all there is to the suspense list, but that's the missing piece with individual suspense boundaries where you can't control how they work together. Now, with suspense list, you can, at least to a certain degree, but the normal things I would want to do, I can do. So I'm pretty happy. Just that like with some values, the first time it renders and re-renders behave slightly different, but whether that's a bug or not, I'm not 100% sure, but I fixed that by adding a key to that component. So it is remounted every time and I do get the same behavior every time. So here's the change I made. And the nice thing in this case, I added the suspense list in the same component as the suspense, but I could add this somewhere higher up and it would still affect these suspense.
Concurrent Rendering and Mode
It doesn't have to be an immediate parent. It will just walk up to component tree again and React will find the first suspense list if it's there and use that setting to work with. So kind of nice. So that addition and the result. So please go and add the suspense list component, play around with the different settings, see how it behaves, see what you like, what you don't like and what makes sense in the kind of applications.
React State and Rendering
React detects state changes in the component tree and determines whether to continue rendering or start over. If a state change occurs, React checks if any APIs like useState or setStates were called, which may require a redraw. To prevent invalid UI states, React provides APIs for state management libraries to notify it of state updates and decide whether to re-render the complete or partial component tree.
If nothing happens, it's still the same process, in the end, the DOM is updated, and the new UI is visible, but if that click appears, we can now execute it much earlier. We don't have to wait for the complete DOM to be updated. Now, if that happens, code over here can execute, and it can do whatever it wants to do. And in the case of an event handler or an AJAX Request Completing, for instance, that could potentially change state again, which would cause React to say, oh well, apparently whatever we were rendering has been invalidated by another state change. We're not even gonna bother continuing with this because it's already invalidated. We're gonna go right back to the start and start rendering everything again. Or, this click event handler code might not change any states for React. So React says, okay well, we executed this bit of other code, all very useful but not interesting for us, and we'll just continue with the current render cycle and we'll display the results of whatever happened to the user. So React kind of detects, are any of our APIs called a use state, set states, any of these things, which cause the component tree to potentially have to redraw. If not, it will continue, but if they did, it will work. And that's where tearing of state comes in. Suppose we've got some states, and let me grab my pen. We've got some state object and there is a value in here. Let's say it has the value one. And this component uses it, and, why doesn't it draw? My pen is not helping. So that component draws. And this component uses that same state. Now, suppose this goes and updates the states. It says, well, that isn't one. That's actually a two. Now, if React would just continue with its rendering without realizing the state has changed, then the components on the left right here would render with one, and this would render with two for the same bit of state, and we would get into some kind of invalid UI where the state was half-updated and half-not. So that's where React offers new APIs for library developers that do state management libraries where you can notify React, well, we did something which caused state to update, evaluate that, and decide whether we need to re-render the complete or partial component tree and not continue or prevent these kinds of errors.
Improving Responsiveness with Start Transition
When we installed React 18 and started using create route, we began using concurrent rendering. However, the prime numbers list remained sluggish. By default, applications behave the same way as before. To make a difference, we can use the start transition API to differentiate between high and low priority work. By assigning the prime number part as a lower priority than the range input value, we can improve responsiveness. This change, achieved by using start transition, allows the list of prime numbers to update only when the value stabilizes, resulting in a more responsive and faster application. The difference between start transition and debounce or throttle is that start transition is not time-based, but rather based on available CPU time. This simple change has a significant impact on the UI and responsiveness of the application.
Now, as soon as we started doing concurrent rendering, we actually, or sorry, I should say, when we installed React 18 and started using create route, we actually started using this rendering, but it didn't really help us with the prime numbers list. If I go here and I go to the 10,000 range and I drag the slider around, it's still just as sluggish as it was with React 17. The reason is this mechanism allows for things to be injected and to detect state updates. But by default, applications will behave exactly the same way. They're not going to behave differently. It's just enabling the possibility. And until we actually start using the APIs to use this, we won't see any difference. And there is one API, or actually two, but they're packaged in a different way. So it's actually one under the hood, which is start transition, which let us start using it. Because basically what React does now is it differentiates between high and low priority work. And by default, everything is high priority. But with start transition, we can say, well, this bunch of work is actually low priority work. And if there is something more important, you can skip this. Just discard it. Don't bother with it. Eventually there won't be any more important work. And whatever work you're doing in that start transition will be done, but only the last bits of it, not everything. So lots of intermediate work can be discarded. And if I look at this, if I drag mouse around, let's actually go to slightly lower numbers. If I drag mouse around on that input type range, we see these prime numbers being redrawn and it determines whether numbers are primes or not. Well, that takes time. Here, that's fine. But with a slightly bigger range, it's okay. All that state, which determines the value of the slider and the list of prime numbers are tied together and they're all updated on high priority. But maybe I can skip updating the list of prime numbers as long as I'm actually changing this slider because as long I'm changing the actual list being rendered isn't that important. And with start-transition, I can say, well this part, the prime number part is actually a lower priority than the value of this range input. And it turns out that doing that is pretty simple. Here's the actual components that's responsible for that list. And you can see there is a prime range here which actually sets the maximum prime number to calculate. And then there is a loop here for each individual number. So that's, where's the components, let's close all of these. Let's in primes, that's in here. And right now, whenever in that prime range, so that top element, that slider, is updated, it will immediately set this max primes, which is normal you state, which renders this component, which immediately creates all those prime number check components and they start doing their thing. Well, we can't do that. Well, let's make that a bit slower or lower priority, I should. Start transition. Start transition takes a callback. So we put that code in there and that's the whole change. So start transition was imported from React. Part of React 18. Part of React 18. Now we go back here with the low numbers. Almost the same behavior. I go to the 10,000 range, which was slow before. I click somewhere and it responds immediately. I drag, the slider responds immediately but the list of prime numbers doesn't update until I stop and it stabilizes. Or if I move the most slowly, it will occasionally have time to update. There, it actually updated, but not really. But now, even if I go to the very large numbers up to a million, I can move this slider around and it responds nice and fast, really reactive. But the list of prime numbers that isn't actually re-computed until the value stabilizes. So, we're really using concurrent mode now. We've made a difference. The value of the slider is high priority. The list of prime numbers is low priority and that's the expensive part. So now it becomes much more responsive, much faster. And eventually, everything will be consistent. And if I go back to the somewhat slower numbers and I move the mouse slowly, you will occasionally see it updates. The larger the numbers become, the more expensive it becomes. So the less frequent it becomes. So Christina asks, how different is this under the hood from debouncing the inputs? And that's a very good question because with react 17 and before debounce or throttle would be the go-to options to do or to solve a performance issue like this. Well, the big difference between debounce and throttle on the one hand and start transition on the other hand is that debounce and throttle are time-based. I could say over here, I want to set this value on debounce after a second delay, which basically means updates this max prime value if that function hasn't been called for a second or if I use throttle, it would be a case of at most once a second, however often it will be called, which means that if I'm on the low numbers, which are really responsive, even here, if I move the slider around, it would take a second before the actual prime numbers are updated, because that's the throttle. But with the start transition, it's not a matter of time. It's a matter of available CPU time. And here there's plenty of time available. So it's really responsive. Here, there is not so much time available because the rendering takes longer. So it just goes as soon as it can. So that's a difference there. So pretty simple change, but it has a pretty big impact on the UI and the responsiveness for the application.
Introduction to Start Transitions
Start transitions should be used judiciously. In some cases, like a responsive slider, it makes sense to use them. However, there is a drawback to start transition - it doesn't provide feedback when the list of prime numbers becomes stale. We'll explore another API that addresses this issue.
Now you typically don't want to start introducing start transactions everywhere. It has to make sense. Like in a case here where I've got the slider which needs to be responsive, but this UI can lag behind that makes sense. In other cases it might not. It really depends on the circumstances whether it makes sense. One drawback of start transition which I'm using now is there is no feedback over here. Like the list of prime numbers isn't updated, but I can't see that it's currently stale. I'm using a stale list there. So after this we'll look at another API which under the hood also gives us a start transition but in a slightly different way so we can actually fix that.
Using the useTransition API in React 18
In this section, we learned how to use the useTransition API in React 18 to start transitions and get the current state of pending transitions. By using useTransition, we can show a specific UI when in a transition state. We made a simple change in the PrimeNumbers component to use useTransition instead of startTransition, and passed the isPending flag to the CheckNumber component to show a sandglass icon during transitions. This change improved the responsiveness of the UI, and we can now see the pending state during transitions. Please make this change in your code. After that, we'll discuss the reasons for having both startTransition and useTransition APIs. Note that useTransition is unrelated to suspense and does not involve throwing promises.
So the result, the responsive slider and, well, list of prime numbers which lags behind a bit. So Alexey asked, can we wrap our array of prime numbers only? I meant to update the state of the component but render our numbers inside start transition. Not sure because you don't render inside of a start transition. We've got all of this code here which executes with the render. The test API is a bit different. The thing is over here, these values are filled based on that max prime. So we're basically making sure that this isn't updated. So the list doesn't actually change. Yeah, I was just asking because like, if you want to see the actual value while we're dragging this UI element, if you want to see it on every single, like every tick, right, like every time you move it even a little bit, but really don't want to like rerender everything. I'll show it. Let me show you how to prime range component works. In here, you can see this is the slider input. There you see on the change, it actually calls to values on change, which is the external handler posting, which we just changed over here. And it has its own setmax prime range, which is its own internal state here, which is used for its own value. So that's updated immediately. The other handler is called, and that's runs in a lower priority because of that start transition. And that's why this one is always up to date and the slider is responsive. And now this one can lag behind. I could have put that transition in here as well, but because basically it appears twice here. So we would have to copy it. But of course over here, you can do exactly the same thing. Duplicates this state saying, I've got well, transition-based version of the state value and the direct version of the state value. Except in this case you still want to prevent this from re-rendering. So you probably have to change this into another pure components and do all the logic in there. By the way, another check you would... or optimization you typically do is check number would be a pure component. In here, it's not. It's really doing its rendering every time. And it's really on every render here calculating whether the value is prime or not. So that's definitely not an optimized version of this component. But if I optimize this, it would be actually much harder to show you the start transition behavior. So start transition is pretty neat for that. But it turns out, there is a second API, useTransition, which we can use, which sort of does the same thing, but gives us a bit more. And the thing it does is, startTransition is just a function we can import, but useTransition is in Hook. And besides letting us start transitions, it will also give us the current state. Are we in a pending transition or not? And that can be useful if you want to render some specific full BEC UI, maybe make it clear that we're in some kind of transition hook state. So how to do that? Actually really easy. That's prime numbers. So instead of startTransition, I'm gonna import useTransition. And I'm going to call that, takes no parameters. It returns two things. an isPending flag and startTransition flag. startTransition like that. So this startTransition works exactly the same as the one we used before. So I don't have to go make any changes there whatsoever. This isPending flag is either true or false whether we're currently in site of some transition. So we could use that. Now it turns out this check number component already has some capabilities there. It already has an optional isPending. And if it does instead of doing the check marker cross for whether it's prime or not, it's going to short circuit that and just show us one of those sandglass icons. So all I need to do is pass that into here. And now if I go back to my PrimeNumbers component and I move that around, you see that we get sand glasses until it actually stabilizes. And if I go to the large numbers, still a responsive slider. well, almost as responsive. Lags behind a bit more, but the UI actually updates. Could also have done something else like maybe show these grayed out or something like that. Maybe not render them. Maybe whatever is appropriate in your UI. With a pretty trivial change. Basically, instead of importing start transition, import use transition and start using that. So, pretty simple to do. Minimal change and it gives us some nice benefits. We get to see that we're in a pending state. Now, please make this change. And after that, I'll briefly talk about why we have both these APIs, because the first time I saw this, we were like, if start transition gives us what use transition already gives us, why do we need both? But it turns out there are some good reasons to have both. But we'll talk about that after we do this. So I'm gonna open up the breakout rooms for five minutes again, and I see there is a question. Does it also, a question from Amir, is it also compatible with suspense? This is really an unrelated API. This has nothing to do with suspense because there are no promises being thrown here. So in that regard, it's completely compatible because they're not related.
Using StartTransition and UseTransition
You don't need to use suspense. In fact, there is no suspense components, or actually there is at the root, but not specifically used for this. StartTransition is faster, but no additional feedback. So, yeah, they both have their advantages and disadvantages and both are useful. With Use Transition, I can show the fact that we're loading a new user here inside of this list and have some more control over how everything appears. The click and the transition here are actually in different suspense boundaries now, so they don't cooperate. But if I go into user details, and let's get rid of these suspense boundaries, now they live in the same suspense boundary. We see it's been grayed out, and we don't see any spinners anymore, but we're inside of a transition, and we control exactly what we want to do.
You don't need to use suspense. In fact, there is no suspense components, or actually there is at the root, but not specifically used for this.
Okay. Everyone's back. Everyone had some nice visual feedback now from the start transition. Yeah, I have some, so basically it looks nicer let's say, to have it rendered after you, you put the dot on some place like left and right. However, I just saw this, after a while when you want to move this dot, so you move the dot, then the table with the numbers is getting rendered or re-rendered and you can't move the dot or the dot is moving really very, very slow. It's not very smooth. Yeah, like. When you have like a big data. Refresh and then go to 100,000. So, for example. So at the beginning. Yeah. So now it's like, yes, but when you release the dot now, yeah and it will render. And now when you try to move it, yeah, as you see, it's getting stuck at some points. It feels like when React gets this opportunity to render, finally render this list, it takes all the resources and it doesn't release those resources to the browser while it's rendering this huge list. It still considers this entire list as a one chunk, like one piece of work, unit of work. Yeah. It stays responsive for me, but it's not quite as responsive as it was before with StarTransition by itself. It doesn't get stuck for me. That said, if I drag now, the first rerender actually takes more time. Yeah, it seems like, because we are defining the hook in the rendering, does it mean that it does the first render and then it triggers the StarTransition? Yes, that's correct. And actually that is a very good point to bring me to the next slide, cause I mentioned I was gonna talk about why we have both StarTransition and UseTransition. Well, as you mentioned, UseTransition is a hook. And that means it can only be used from a hook or a functional component, but it has to start with a functional component somewhere. It can't start from some library or something. And StarTransition is just a function that could start from anywhere. It could start from a class-based component, from a functional component, from some other library which knows about React. So StartTransition is more flexible. The other thing, StartTransition starts the transition and then it's done. UseTransition, you call the StartTransition which is returned from UseTransition, it also starts the transition, but then it has to trigger one additional render cycle with that isPending flag set to true. So you get one additional render. And in this case, I've got a pretty large set of prime numbers here. I think I'm rendering 10,000 of them. So, that first time I moved it, it first has to render those same 10,000 components so that again, all with the isPending flag is true. And only then can it stop rendering those and we're in the transition. So it's that first re-render which actually makes it appear slower or not just appear slower, it is slower. So the useTransition hook is a bit slower because of the additional render, but it does give us additional feedback and it needs to be used from a component. StartTransition is faster, but no additional feedback. So, yeah, they both have their advantages and disadvantages and both are useful. Now, it turns out with transactions, you can do more specifically UI transactions because in this case, we actually need the feedback because in this user list, if I click on the user, I see Loading Spinners, but maybe I would actually want to show the fact that we're loading a new user here inside of this list and have some more control over how everything appears, maybe not show spinners here, but just gray this out or something like that. Well, it turns out, with Use Transition, I can do exactly that. So let's go and do that. So we'll close this. I'll go back to my user list and inside this click where I said to select the user, I can actually start doing that inside of a transaction. So we can start import use transition, just like before. Actually, that's just, I closed it. Just copy that line, grab that start transition here. So again, that should we fed arrow like this and one more brace. And we've got a start transition in there. And now I could do something with that is pending flag and maybe pass that into here. So that could display itself a little differently. And it's not on the interface yet. So let's add it there. There's a Boolean. Retrieve it from the props. And then I could maybe make it slightly transparent with a dynamic style. So opacity. If we're pending we'll set it to 0.2 or something, and otherwise we'll leave it to the default. By setting it to undefined we're basically not setting it. Now, in theory, that should work. In practice, it doesn't quite yet. Let me show you what happens first. Nothing new. Why is that? Well, because the user details in the favorite movies have their own suspense boundaries, so the click and the transition here are actually in different suspense boundaries now, so they don't cooperate. But if I go into user details, and let's get rid of these suspense boundaries, suspense boundary there, now they live in the same suspense boundary and will actually get a bit more. So I click on the user. We see it's been grayed out, and we don't see any spinners anymore, but we're inside of a transition, and we control exactly what we want to do. Now, that was already loaded.
Improving User Experience with useTransition
To improve the user experience, we can update the active style of the user immediately upon clicking, rather than waiting for the transition to complete. This can be achieved by duplicating the state and using the useTransition hook. By using useTransition, we can have full control over how the user details are updated and prevent spinners from appearing. We can even render additional indicators, such as an hourglass, conditionally to show that the user is loading. It's important to duplicate the state and set the state inside the transaction for the data we don't care about immediately, and set the state outside the transaction for the data we do care about immediately. This change can significantly improve the responsiveness of the application. Please make this change and I'll see you all back here in five minutes.
Let me do this again. The only thing is, if I click on the user, like I click on Spares now, the loop stays highlighted until that transition is complete. So we want the active style not after all of this is loaded, we want to update that slightly sooner. So we actually have to do a bit more work here because that active style here is done over here based on the selected user which we're setting but in a lower priority. So this does actually rerender with that selected user until that transition has finished. So what we want to do instead is say well, we need to double that state. We need the selected user or user ID. So let's copy this. We need the selected user ID and set selected user ID. The user ID is a number, so we can default that to NAN, not a number. And then in here, we can compare the user dot ID to the selected user ID. And then outside of the transaction, we set the ID. And that's the user dot ID. And that return is not actually needed. It doesn't return anything, anyway. So now the user is highlighted as soon as I click on him. And the details are updated inside of the transaction. And I get to control exactly how they're updated. But notice how no spinners appearing now. The suspense boundaries with their fallbacks don't actually start any of the fallback rendering. Because I'm using the use transition hook, they basically leave it up to me to decide how I want to feed the user. I can move it as well. I can leave it in. I can remove it. Doesn't really matter. It doesn't do anything because there are no suspense boundaries inside of it. And it's basically completely up to me. But that's another nice capability I have, which I didn't have before. So instead of using the suspense list, I can go here and I can move the request. And you can see, as you can see, that's what I did. My list, to coordinate how different suspense components work, whether it's one or more, doesn't matter. They all enlist in this transaction because they're all part of the same suspense boundary. But remember, they have to be part of the same suspense boundary. If I put one back in, say for the movie, then that's going to have its own behavior. Now, I was actually expecting to see a spinner here. But for some reason, I'm not seeing the spinner. Interesting. Is that because of the suspense list? I doubt it, but let's check. No, so I guess the use transition overrules the whole suspense and suspense list. As long as it's part of it. If I also enable this suspense, it's not part of it. Now we do get the spinners. Remove all of those again. So, which I actually think is pretty neat behavior. Now you get full control and you can even do some other rendering. Maybe in here you want to render the fact that this user is loading by another indicator, something like... Insert that hourglass there and only do it conditionally. It's pending, then we want the hourglass. Else we want nothing. So now, if I click rendering them full, that wasn't exactly my intent, so let's add this check. So now I'm rendering that hourglass only for the user I'm currently loading. Whatever you want. Complete freedom because you get complete control now due to that is pending flag, which I really like. So. That same change which I just made, but remember, you kind of have to duplicate the state, set the state inside of transaction for the stuff you don't care about immediately and set inside of the transition and set the state outside of the transition for the stuff you do care about immediately. And remember, you do get re rendered and so. If things are slow because of render times, then you still won't get all the benefits. So the UserDetails component without the suspense and the suspense Lift. And the result. So please go and do this. I'll open up the breakout rooms and after that we're done. This is the last exercise we're going to do. So just to wrap up and we're all done for this workshop. But please go and do this change and I'll see you all back here in five minutes. And that's everyone back. So there was one question I missed before from Alexi. How does StartTransition know if data is ready and promises are ready to be resolved? I haven't actually looked into the React source code, how this exactly works. But my guess is that it's looks for the closest suspense boundary in the parent and it checks its state. Is it suspended? Then it knows that the expanding flag should be true. And when that resolves, it will reset that flag and the components are rendered again.
Interaction between Suspense and Start Transition
Suspense and start transition work together to handle the fallback UI and communicate between each other. The React source code is complex and developed by talented individuals.
And of course that last part is automatic already because of suspense. It's just that suspense knows about start transition as well or at least the use transition hook because it doesn't show its fallback UI. So I'm not sure how they communicate, but somehow there is communication between the two on that. Suspense should do a bit less and start transition should be a bit more. Well, magic. I'm not sure about magic, but let's put it this way. If you dive into the React source code, it's complex. It's not a trivial bit of code, not by any stretch of the imagination. There's some very clever people working on that.