Deep Diving on Concurrent React


Writing fluid user interfaces becomes more and more challenging as the application complexity increases. In this talk, we’ll explore how proper scheduling improves your app’s experience by diving into some of the concurrent React features, understanding their rationales, and how they work under the hood.



Hello React Advanced, it's great to be here. Finally, one of my favorite conferences. So thanks for having me. Yeah, we're here to talk about Concurrent React. I guess it's going to be the second talk to date discussing a little bit of the internals of React. So if you love Stasia's talks, you're probably going to like this one. Hopefully it's going to be probably as fun as that. So this is me. I'm a front-end engineer at Medallia. I also volunteer at TechLabs, and you can find me everywhere as Ydecominator. And by the way, all the links for this session, including the slides, are available on this QR code. So feel free to follow up. And one quick heads up is we're supposed to be diving deep here, so whenever you feel like some content needs more discussions or explanations, look for this emoji. It means we're going to have further discussions. So cool. I'd like, by asking you, so if you guys had to summarize Concurrent React in one word or expression, what you would go with? For example, we watched Stasia's talks, and we saw that fibers are units of work. So if you had to do a similar exercise with Concurrent React, what would you do? So for that, I really want to have your help. So this is the QR code for you to input your opinions. And I have 30 seconds. So yeah, I'd love to hear from you. What do you think about Concurrent React? One word, one expression, what it's all about. And yeah, it's funny because 40 seconds sounds like a lot of time, but when you have to strike a conversation in the meantime. So yeah, another 10 seconds to go. I still see some phones. Okay, cool. So let's take one step back and talk about the main thread. And let's talk about what's running on main thread. We've probably seen that kind of stuff before when profiling our apps. Those are the long tasks or what we see on DevTools with those red flags because you're taking too much time for long thread. And the effect of that in our apps is terrible. So because here, this example, we have some input fields. We have check boxes. We have links and buttons. And basically, when we have long tasks running on our main thread, all of them are blocked. So our app becomes irresponsive. And you might say this is a virtually created example, but it actually happens a lot of there in real apps. And that's, for example, why we have things like rage clicking and other user behaviors that are reacting to that. And not only it happens a lot of there, but we even have metrics. So we have, for example, first input delay and other metrics that we've probably seen Lighthouse or other tools that helps us spot when this happens and et cetera. And not only we have metrics, but we have research around that. And for example, slow first input delays can be seven times worse on mobile devices. And not only this, but long tasks, they also delay TTI and other metrics. And again, on mobile, they can be up to 12 times longer than on desktops. And last but not least, on old devices, on old mobile devices, they could, half of the load time could be spent on long tasks. And it's already bad when you say like that, but when you see some business outcomes of that, like for example, your conversion rate, it's even worse. So we get to the point where we want to avoid blocking the main thread. So how can we do that? To do that, we start discussing some task running strategies. So let's say we have four tasks we want to run in the browser, A, B, C, and D. We could, for example, go with a parallel approach. So basically we have multiple tasks running on multiple CPU cores at the same time. We could have concurrency. That is, we have one single thread, but we quickly switch between the tasks to give the idea of concurrency. And we could have scheduling that is pretty much like concurrency, but we have an extra piece of software called a scheduler assigning different priorities to different tasks and organizing the whole thing. So let's start by the other approach, parallelism. So parallelism in the browsers, as you probably know, happens with workers. So workers, they have a few gadgets. So data exchange is via message passing. So we have that thing called post message that you probably know. But the first gadget of workers is that we don't have access to the variables or the code from the thread that created them. And also we don't have access to the DOM. So making UI changes from a worker is really, really complicated and sometimes barely possible. But we have two abstractions that we could go with when thinking about workers. We have actors and shared memory. So the first of them, actors, you've probably heard about actors from other languages like Elixir or other, especially in the back end it's really common. So actor is an abstraction where each actor fully owns the data it is operating on. And they only see, send, and receive messages. And that's pretty much it. And in the browser, we can think as, for example, the main thread is the actor that owns the DOM and the UI. But the first gadget is that post message is a fire and forget mechanism. So it doesn't have any built-in understanding of request and response and tracking that. And the second thing is, okay, we offload code from the main thread to make things faster. But at the same time, this communication, because it happens via copying, it has a communication overhead. So we have to balance that. And also the worker we are sending things to, it might be busy. So that's another thing we should take into consideration. On the other end, we have shared memory. And in the browser, we have one type called shared array buffer. And that's really great because, for example, if we send a shared array buffer via post message, on the other end, you're going to get a handle to the exact same memory chunk. So that's great. But the thing is, because of the way how the web was built and how the browsers were built, there's no built-in APIs with concurrent access in mind. So because of that, we end up having to build our own concurrent data structures like mutexes and stuff. And not only that, but we're not doing arrays or objects or anything that we're familiar with in JavaScript. We're just handling a series of bytes. And someone could say, okay, what about WASM? WASM is great, and I agree. And that's actually probably the best experience we can get for a shared memory model. But again, it doesn't offer the comfort of JavaScript, and by comfort I mean the familiarity. So a lot of front-end engineers when jumping into WASM, there's a lot of learning curve. And probably the most important thing is it's faster than JavaScript when you stay within WASM. But the more you have to cross over the line and do any DOM manipulation or anything that has to do with the UI, the more it starts to get slower. And then sometimes it gets to a point that you realize that some of the fastest low-level WASM implementations might be slower than regular libraries like React, Svelte, or whatever. So before we move forward, I have to say though that there is a lot of interesting stuff out there happening with workers and WASM. So we have the Atomics type, and we have open-source stuff like WorkerDOM and Conlink. They're amazing. If you're working with web workers, you should definitely check them out. But it turns out that workers are amazing for data processing and crunching numbers and that kind of thing. But they end up being difficult for UI-related stuff. And it's sometimes harder than simply adjusting the work you've got to do for a scheduler. So that's when we get to the second part, concurrency and scheduling. So back to the question I asked before. Fingers crossed because this is going to be live and hopefully it's going to work. So those are your opinions. So okay, some emojis, of course. Concurrent React is about emojis. But okay, I think Concurrent React is confusing. See that? So parallelism, pointless, render, priorities. I like priorities. So I'd like to show you my, and thanks by the way for participating, my take on Concurrent React is scheduling. And I only have 20 minutes, so I've grouped a few concepts that I love about Concurrent React. So we're going to be exploring quickly the heuristics. We're going to be talking about priority levels and render lanes. So okay, we talked about workers and we saw there's no workers, wasm, or anything related to parallelism. So what do we have? We have this cooperative multitasking model where we have a single interruptible rendering thread. And because it's interruptible, rendering can be interleaved with other work that is happening on the main thread, including other renders from React. And also because of that, an update can happen in the background without blocking response to user input or that kind of thing. And one of the most interesting things, I think, is the heuristics behind that. Because React yields the execution back to the main thread every five milliseconds. And I admit the first time I saw that, it sounded a lot like one of those magic numbers we usually do in the front end, especially in CSS. But it turns out that it's smaller than a single frame, even when you're running on 120 FPS devices. So that's what makes that in practice rendering is interruptible. And that's really amazing. Another thing are the priority levels. So we see them in the source of the scheduler, but you can see them repeated throughout the whole framework. So we can see them in the reconciler package, we can see them in the renders like React Don, and we can even see them in the dev tools. And basically they range from immediate to idle, and each of them have different assigned priorities that are basically going to tell React when something should be done. Last but not least, render lanes, which are also amazing abstraction. And if you watch the Tasia talks, you're probably wondering what that part of the code was all about. So render lanes are an abstraction built around bitmasks. So one lane bits one bit in a bitmask, and in React, each update is assigned to one lane. And because of that, updates in the same lane, they render in the same batch, and in different lanes you get different batches. And the first good thing you get is because it's one bitmask, so you have 31 levels of granularity. And the other amazing thing is that basically they allow React to choose whether to run multiple transitions in a single batch or in separate batches. And this whole thing reduces the overhead of having multiple layout passes, multiple starry calculations, and multiple paints in the browser. That was a lot, right? And I myself, when I went through some of these concepts, I was like really, really mind-blown. And for me it was all amazing and really interesting, but at the same time I couldn't not remember this talk by Kites. It's called But You're Not Facebook. So we're not building schedulers, we're not doing that kind of stuff on a daily basis. So how can we, like the other 99% front-end engineers, benefit from these on everyday projects? So this takes us to the next part, that is scheduling in React for the rest of us. So again, there are many, many scenarios where I think each of the concurrent features can be amazing, but I grouped four of them here. And the first one is when we have to handle a lot of data. So I had made a lot out there, we saw a lot of examples that in the first moment they saw no practical. For example, we see how to find primes in our render methods and update that and optimize that with the transition. Or how can we run complex algorithms for cracking passwords or even rendering like huge stuff and those examples are great for benchmarking and showing what you can do with concurrent React. But at the same time, it's important for us to remember that we front-end engineers, we render a lot of data points, for example, on things. Or sometimes we have to render things in a canvas and we don't have a screen canvas available. Or sometimes we just have to process a lot of data. So one example is a dashboard. So we are usually building dashboards, for example. And in this one, basically I'm rendering the amount of visitors per day on the website. And as you can see, the animation is a bit laggy because of the amount of data I'm updating. And there's not a lot of magic in this component. I have a simple effect, I have some state and a non-change handler. So if I change that to use a transition, we can see that, for example, first, the animation is always smooth no matter what. And also no matter how much data I have, it's responsive all the time. And I myself, when I first saw transitions, I really wish I could go back in time. For example, I was working on an app about five or six years ago where we had about 100,000 data points plotted on a map. And not only that, we had to support searching and filtering. So back then we used workers to do a lot of data and we used Redux, Saga, and its utilities to optimize things and even the bouncing. But it could have optimized a lot of it just using transitions. And another example was this app I was building three years ago, something like that. It was the game admin panel for an online game where you have thousands of players sending thousands of messages. And as anatomy, you were supposed to search and filter all of those messages. So again, we had to fall back to virtualizing a lot of lists and we overdid memorization everywhere with a lot of use memos and a lot of callbacks. But it could have optimized some of that using transitions. Another thing is tackling wasted renders. So we usually think about use callback, use memo, or that kind of thing for tackling wasted renders. Or even, for example, changing the props we pass using react.memo and that kind of stuff. But who here thinks using external store hook? Wow. Okay. Cool. So it's a hook that was first marketed out there as part of concurrent React for library maintainers. And actually we saw some state libraries adopting them. For example, Redux itself started using it from V8 and Vultio and many others. But this kind of brought the question, how could we use it? One thing we do use a lot is React Router, right? So I guess most of us are doing React Router for quite a few hands. So we probably know use location. That is a hook that we can get, for example, path name, hash, and other information about our route. But use location is kind of an over-returning hook. So it's going to give us a lot of information, even though we just need some of it. And if we use it like that, the result is, for example, you can see here that even though I'm just updating the hash, the path name components is going to re-render because I'm watching that hook. So how could we change that? We could go back to our original example and replace it with a new hook called use history selector. And I created use history selector using use history plus use sync external store. And now I can create selectors for path name and hash. And the result is that as I click now, hash is the only component to re-render. So I save some re-renders. And this was a really simple example. But in a huge scale app, this saves us a lot. Another thing are the hydration improvements. So if you've been doing SSR in React before, you know that hydration could only begin after the whole data was fetched for the whole page. And this also affected how users interact because they could only start interacting after that. And the other bad thing is that if you had a component or some parts of your app that loaded faster, they would have to wait for the slow ones. Now we have selective hydration. So now React won't wait for one component to load to continue streaming the rest of the HTML for the rest of that page. And not only that, but React is smart enough to prioritize hydrating the parts that the users interact with first. And that's amazing. And also thanks to concurrent React, the components can become interactive faster because they allow the browser, because now the browser can do other work at the same time. And the final result for our users, for example, is lower first input delay or lower interaction to next page, which is also another amazing metric. And we even have people out there doing that. Verso, for example, they revamped the Next.js website using this technique. Last but not least, the new profiler. Because not only we want to build apps, but we also want to properly figure out what's happening with our apps. So we have this scheduling tab that we can use, for example, to properly see how our transitions are going. And one of my favorite parts are the new hints that we have. So for example, we can� the profiler can see that we have a long task that could be potentially moved on to transition. And then this could be like an interesting optimization. And I'm really, really hyped about all these things, but I have to say that I'm even more hyped about what's coming down the line. So we're going to have IOLibraries like React Fetch. I mean, it's been out there for a while now, but it's going to happen at some point, I guess. We're going to have built-in cache for data, for components to integrate with suspense. One of my favorite parts that is suspense for CPU bound trees. So if you profile your app and you figure out that some part of your tree is really going to take a lot of time, you can go ahead of time and then fall back without even trying to render. That's amazing. We're going to have more hooks for library maintainers, like using search and effect. The offscreen component, that's also another one that I love, that it's basically a way to assign idle priority, as the ones we saw before, in your code to a part of your tree. Server components, which could be lots of other talks just to address them. And it's not a React thing, but something I'm really, really hyped about is the native scheduling primitives in the browser. So who here has seen this API umbrella before? It's called Prioritize Task Scheduling. If you haven't, I would definitely recommend you to check out. But it's basically a more robust way to do scheduling in the browser, other than, for example, requesting idle callbacks and stuff. And it's going to be something that is promise-based and integrated directly into the event loop. And not only that, but it's aligned with the work of the React core team and other teams like Polymer, the guys from Google Maps, and even the web standards community. And this API is an umbrella for a lot of interesting stuff, like APIs for using execution to event loop, APIs for scheduling tasks, APIs for checking if the browser is busy handling some kind of user input, and et cetera. And we even have people out there using it. So we have, for example, Airbnb, Facebook, who was one of the major contributors for isInputPanning, for example, they're also doing that. And even we have libraries strongly inspired by the spec of that API umbrella, like the main thread scheduling. That was a lot. So a few closing notes. The first one is, I'd like to start by linking this talk by Rich Harris. I love this talk. It's one of the most brilliant I've watched in the last couple years. It's called Rethinking Reactivity. And in this talk he says that React is not reactive. And yes, this whole thing's been out there for a while now. And yes, he's right, it's not reactive because of the whole nature of virtual Dawn and how it works. But it is concurrent. And it might be enough for your case. Hold on, and a lot of things have been optimized well enough for a lot of cases, so that might be yours. Another thing is this quote by Guilherme from a couple years ago when he said that React was such an amazing idea that we would spend the rest of the day exploring its implications and applications. And I think that, for example, the fact that React is strongly related to this scheduling API, which is amazing, is one of those signs. So React has not only pushed other frameworks to the future, but also the whole web. And I think that's amazing to see. For the next conclusion, who has seen this recent RFC that is kind of the hot stuff on Twitter? Everyone is talking about first class support for promises and React use and, yeah, some people have good opinions, a lot of different opinions about that. So I have one really simple example of code, and I don't intend to read everything, but basically what I'm doing here is creating a really, really simple cache, and it's in TypeScript, and, yeah, I'm throwing promises and et cetera, but I created a hook called use promise in this code. And we can use that code, I know the font size is not the best one here, but basically I have this delay and I'm using promise with any promise, and the result is it is suspending and et cetera. And when you see that, it might sound a lot like, okay, this is React use. So yeah, this is like, of course, a very, very, very simpler version of React use, but the reason why I'm showing that is that I really think that understanding those internals and the rationales behind them really helps us creating our very own abstractions. And this is really, really amazing, and one example is the first class support for promises. Another thing is that scheduling doesn't necessarily mean better performance. Just like Reactivity or any other strategies like using or not virtual DOM, it has its drawbacks. So because of that, it's always important that we keep in mind the cliche that there is no silver bullet. And because there's no silver bullet, it's important that we identify our core metrics and what's really important for us. These days there's a lot of information out there, so you're going to see amazing people building amazing tools with a lot of amazing opinions, and I know it's really, really easy to feel lost amongst so many different things, so many different thoughts that all look amazing. So it's important for us building apps and even building our own libraries sometimes that we correlate those performance metrics with business metrics, like conversion rates and et cetera, because that's what matters most of the times in the end for our users. The slides for this session are available on my speaker deck profile. The link was in the other link. And probably the biggest takeaway here is that I have stickers. So if you have opinions about scheduling Concurrent React questions, if you want to talk more about that performance, I'll be around not only here but in the speaker booth and in the event. So that's all I had for now. Thank you so much for having me, React Advanced. Thank you, thank you, thank you. Please step into my office. We have time for maybe one or two questions, so please join us as we get. I'm going to wait to see if any come up in the Slido. This is all very exciting. Obviously it's the future. I'm curious to know, I love that you showed us sort of more practical examples for the rest of us. Did your interest in this come from stuff you encountered at work and then it was helpful, or is it stuff you've been interested on the side? Actually it was a very, very personal experience a couple years ago. So when Suspense was released, and I was watching the conference and et cetera, and we even had George Palmer and others presenting all of it, and it was really interesting. And when I saw that, it reminded me a lot of something a friend of mine had shown me a couple months before. And I approached him after watching that. I was like, they just announced it. How were we doing that a couple months ago? And then he basically mentioned, oh, I saw that by reading the pull requests related to Suspense and et cetera. And that's when I started seeing the benefit of basically stalking what was happening in the source code of React and following that up. And that in the end helped me, for example, optimizing some of the apps I was working on back then and et cetera. So yeah, it was back in 2019. Okay, and one final question, and then you'll be at the speakers lounge, which is how would you recommend someone start with concurrent mode? Oh, that's a tricky one. So actually, I would say try to spot where you go through all of the concurrent features that by now there are a lot of them, and try to spot in your app what would be a potential feature for one or another. And also see if your app is concurrent mode compliant. So start by wrapping things with strict mode and see how your app reacts to that. And then, yeah. And then go from that, like one feature from another. Maybe transitions are one of the most straightforward ones to go. But yeah, try to see where they all fit. Not only just goal, because everyone on Twitter is talking about that. Yeah. Thank you so much for your time. Mateus will be at the speaker Q&A next to reception for more questions. We have to move on because of time. But another big round of applause. Thank you.
29 min
21 Oct, 2022

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

Workshops on related topic