Concurrent React Made Easy

Rate this content
Bookmark

UI’s are composed of fast parts, and slow parts in terms of how responsive they are to user interaction. React's concurrent renderer decouples the fast parts from the slow parts by allowing us to render the slow parts in the background without blocking the fast parts, so that each part can respond to user interaction at its own pace. In this talk, we'll explore Concurrent React, understand what problems it solves, how it works and how to leverage it through the use of concurrent features.

23 min
23 Oct, 2023

Video Summary and Transcription

Today's Talk introduces concurrent React and highlights the importance of fast and slow updates in user interfaces. It explains how concurrent rendering improves UI performance by allowing fast updates to proceed without being blocked by slow updates. The concept of assigning priorities to renders is discussed, with high priority renders being synchronous and low priority renders being interruptible. The Talk also mentions the benefits of using concurrent features in navigation and list filtering. Overall, concurrent React enhances rendering with interruptibility and prioritization, making the application feel faster and more responsive.

Available in Español

1. Introduction to Concurrent React and Updates

Short description:

Today, I'll be talking about concurrent React and the importance of fast and slow updates in user interfaces. We showcase examples of fast updates and the impact of heavy computations on the main thread. We also explore a demo where both fast and slow updates coexist.

Hello, everybody. My name is Henrique Núñez. I'm a software developer at Codeminer 42, and today I'll be talking about concurrent React. So come with me.

So we'll start by drawing our attention to user interfaces and interactions with them. Whenever users interact with the UI, it updates itself in response to these interactions, and these updates can be divided into two categories, fast updates and slow updates, in terms of how long it takes to process them. For example, updating input fields, buttons, toggles, and sliders. When we consider these updates in isolation, they are very fast. On the other hand, filtering a huge list, updating a dashboard, recalculating cells in a spreadsheet or performing navigations usually take a reasonable amount of time to complete and thus can be considered slow, especially when compared with fast updates.

Now, let's see this in practice. First, we have a demo where we showcase some examples of fast updates. Notice that we click on the bottom, we write in the text input, we drag the slider, and the updates in response to our interactions are processed instantly. There's no delay. Additionally, we also have two different kinds of animations in this demo. A JS animation and a CSS animation that although they are not a direct response to any interaction of ours, they are also updating.

Now, this is a slice of a profile that I was taken from this demo. Notice that in the interaction section we can see our click and below that in the main thread section we can verify that processing the corresponding update was indeed very fast, under 2 milliseconds. In the second demo, we have a button that triggers a heavy computation, that is, it's an update that takes a long time to be processed. In this case, it's an artificial example that will serve to explain some things that will come next, but it could very well be any other example that we talked about previously like filtering a huge list, so bear with me for now. This is the corresponding profile of the second demo. Notice that this update takes 2 seconds, which is a very long time for a UI update, especially considering that it blocks the main thread. This third demo is pretty much the previous one, but not. Notice that we have programmatic control over how long this heavy computation takes, as the first button initiates the computation and the second button finishes it. It's important to make it clear that even though in this case we can control how long the computation will take, it works pretty much like the previous demo. And as we can see in this profile, this computation still blocks the main thread. Now, I want to show you what happens when we have a situation where both fast and slow updates coexist. This demo is a combination of the previous ones, where we have both fast and slow updates as examples. In this demo, when we start processing this slow update, the entire UI freezes, and all the fast updates can only be processed after the slow update has finished. The clicks in the bottom, the text that was written in the text input, interactions with the slider, they only get processed after the heavy computation is done. The only thing that keeps updating despite blocking the main thread is the CSS animation, and only because it takes place on the GPU.

2. Introduction to Concurrent Rendering

Short description:

But everything else that relies on the main thread to be processed gets completely frozen. It only takes a single slow update to slow down your entire UI. Both fast and slow updates are coupled to each other because of synchronous rendering. With concurrent rendering, even though you're processing the same slow update, it doesn't block the fast updates anymore. The heavy task is split into smaller chunks, allowing other work to be done in between. Two demos showcase the benefits of concurrent rendering in navigation and list filtering.

But everything else that relies on the main thread to be processed gets completely frozen. And the key point here is the following. It only takes a single slow update to slow down your entire UI. It doesn't matter how well crafted your user interface is, how optimized all your components are, because your UI is always a single slow update away from a bad user experience. And this is the main challenge we're facing here. This is the problem we're set to solve.

As you can see, in our current setting, both fast and slow updates are coupled to each in the sense that the slow updates end up blocking the fast ones. Now, the reason this happens is because the default approach React uses for rendering in most situations, which also, for many UI frameworks, is the only approach available, is to render things synchronously. With synchronous rendering, once React starts rendering an update, it will run to completion, completely blocking the main thread until the render is finished. So, in practice, this means that no matter how long the render takes to complete, any user interaction that occurs during the render will have to wait for it, regardless of how fast or urgent responding to them would be.

Going back to the fast and slow updates, now, what if we could decouple them? What if there was a way to let each update be processed at its own pace? Enter concurrent react. Now, I want you guys to pay close attention to this next demo here because this is the same demo that we saw before, but now there's a twist. Instead of using synchronous rendering, we are using concurrent rendering. Notice that now, even though you're processing the very same slow update as before, it doesn't block the fast updates anymore. While the heavy computation is still running, we can now still interact with other parts of the UI and they remain responsive. Let's take a look at this demos profile. What we see here is the heavy task being processed. But now, instead of blocking the main thread, it is split into smaller chunks and this splitting into smaller chunks let us fit other work in between these chunks. Now, going forward, I will show you two more demos running with both synchronous and concurrent rendering so we can make some more comparisons. In this first demo, we have an example of a navigation where navigating to different pages by clicking on the sidebar takes a long time. When using synchronous rendering, the navigation blocks other interactions from being processed. So, not only we have to wait before we navigate to a different page, but also the sidebar sidebar's hover effect doesn't work. When using concurrent rendering now, which is what we're doing in the same example, but now with concurrent rendering, even though the navigation still takes a while to be processed, the sidebar is kept fully functional. And even if we change our minds halfway through a navigation, which is a pretty common thing for users to do, right, and we want to navigate to a different page instead, we can easily do so without having to wait for the previous navigations to complete, because concurrent React will abort previous in-used renders. In the second demo we have the classic huge list filtering example. And, of course, as the list has several items, re-rendering it is slow. So when we type we get this jank, you know, where the search bar freezes briefly. Now, in the second version with concurrent rendering, the search bar is kept fully responsive while the list is rendering. Which, I think you all are going to agree, it makes up for a much better user experience. Now, you might be wondering, like, how this all works, right? And we'll get to that right now.

3. Enhancing Rendering with Concurrent React

Short description:

Concurrent React enhances rendering with interruptibility and prioritization. It allows rendering updates to be stopped and resumed, and it prioritizes the most urgent renders. This keeps the user interface responsive.

Concurrent React enhances rendering with two new features, namely, interruptability and prioritization. Interruptibility is about being able to stop a render halfway through to do other things and then resuming it later. Prioritization is about rendering the most urgent things first. Now, by combining both, we're able to start rendering an update, like, let's say, the computation from the demo. And then, if another more urgent update is skewed, I don't know, like, interacting with the bottom with a text input, we can interrupt what we're doing, cater to these more urgent renders, and, once we're done, we go back to what we were doing before. And this is essentially what keeps the user interface responsive.

4. Assigning Priorities to Renders

Short description:

React allows us to mark renders as either High Priority or Low Priority. High Priority renders are synchronous and block the main thread, while Low Priority renders are interruptible and do not block the main thread. Using an analogy with Git branches, features are similar to low priority updates, which can be interrupted by high priority updates. Assigning a high priority to fast updates and a low priority to slow updates decouples them and keeps the application responsive. Concurrent React uses concurrent features to assign priorities to updates.

To achieve that, React allows us to mark renders as either High Priority or Low Priority. A quick but important disclaimer, though. Actually, in reality, we have more than two priority levels. But for most purposes in user len, collapsing them into two levels suffices. Which is exactly what we're gonna do. We're only gonna be talking about two priority levels in this talk.

Moving on, High Priority renders are just the normal renders we're used to pre-React18. So, they are synchronous, they block the main thread, which means they cannot be interrupted. And they also interrupt Low Priority renders, which is what we're gonna see next.

Low Priority renders, on the other hand, they are interruptible, which means that they do not block the main thread anymore. And this is also important. They only start running after all High Priority renders haven't been taken care of, you know? So like, this is what makes them work with that idea of background rendering, so to speak.

I believe that an interesting way for us to understand this is using the following analogy. Let's say we're developing an application and we're using Git to track its code base. I believe everybody's used to doing that nowadays, right? So, there's a main branch that represents the code that's in production. And when we want to write a new feature, we create a feature branch of main, like, I dunno, feature slash awesome feature, and when we're done working on it, we merge it back to main. Pretty straightforward. Now, whenever there's some critical issue in production, we create a hotfix of main, like, hotfix slash fix nasty bug, and when we're done with it, we also merge it back to main. This is also pretty standard.

Now, what happens when we're working on some feature and suddenly, we have to ship a hotfix. This is not an uncommon situation, right? I think everybody has already gone through this kind of thing. So, shipping the hotfix is definitely more urgent than delivering the features. Supposing we're responsible for shipping the hotfix, we first have to interrupt our work on the feature and start working on the hotfix until we complete and merge it. Only after we do that, it's only after we ship the hotfix, is that we can go back to working on the feature.

Now, if you'll be probably paying attention, you probably noticed that features are similar to low priority updates, as working on the feature branch, which is analogous to low priority rendering, may be interrupted at any time by the need to ship a hotfix, which in its turn equates to high priority updates. With all that in mind, the trick to decouple the fast updates from the slow updates is basically assigning a high priority to the fast updates and a low priority to the slow updates, because this way we can keep the application responsive by preventing the slow updates from blocking the fast ones. At the slow updates, by being assigned a low priority, we will be taken care of in the background and may be interrupted whenever we need to tend to the fast updates.

Now that we understand the problem we're solving, the solution itself and how it works, I think it's time to take a look at how we can actually make use of concurrent rendering and practice. In Concurrent React, to assign priorities to updates, we use concurrent features. So let's go back to some of the previous examples we saw before and see how they're actually implemented using concurrent features.

5. Using Concurrent Features

Short description:

In the navigation example, using concurrent features allows us to make navigations a low priority update, preventing them from blocking the sidebar. In the list filter example, by marking the renders of the filtered list as low priority, high priority updates to the search bar are prioritized. Concurrent rendering doesn't make the application faster, but it makes it feel faster and more responsive. When typing in the list filter example, high priority renders run without interruption, while low priority renders are interrupted by subsequent high priority updates.

So going back to the navigation example, without using any concurrent features, every update is a high priority. So as we can see in this first example, because we're using a plain old USETAPE to track the currently selected page, renders that are triggered by navigating to a different page are synchronous, and thus, they are blocking. Once again, notice that the navigation blocks interactions with the sidebar.

Now, when we use concurrent features in this next version, in this case we're using start transition, we can make navigations a low priority update, so that they don't block the sidebar from updating anymore, while also being able to show a loading state with the isPending flag, which is why you see the screen getting dim, you know, with this kind of grayish overlay. And, in this version, we've added a new state, delayedPage, and we're going to use that state to trigger the low priority update, which means that we're actually going to use this update to re-render the page itself. And then, by using the useTransition hook, we have access first to the isPending flag, that tells us when there's a low priority render pending, and also to the startTransition function that allows us to mark a state update as low priority, so that its corresponding render, that is, the render that's triggered by this update, will be a low priority render, and thus, non-blocking.

In this second example, once again, we're filtering a huge list, that, due to the number of components it has to render, is quite slow, and without using any concurrent features, rendering the filtered list blocks the updates to the search bar, which is just what we saw before, right? That's why it kind of freezes. Now, in the second version, by using the Use Deferred Value hook, and passing the deferred filter that we get from the hook to the filter list instead, we're marking its renders as low priority, and now the yield to the high priority updates to the search bar. Notice how in these two examples, although Concurrent Render doesn't actually make the application faster, you know, it doesn't speed up anything, it makes it feel faster, by making it more responsive, and this is very important.

Okay, so next I wanna dive in a little bit deeper in this list filter example, so that we can watch more closely how this process work. Okay, so this is gonna set the stage for what we're gonna see next. So in here, I'm now logging to the console when both high and low priority updates start and finish rendering. And I'm also highlighting when renders are interrupted. Let's see this step-by-step. First, when we type the first letter H, as we're calling a set state to set the filter, this causes a high priority render, which runs from start to finish without stopping. Like without being interrupted. Notice that in this first high priority render, only filter has updated. But the third filter still has its old value, which is just an empty string. Then, after the high priority update finishes rendering, React starts the low priority render, where both filter and the third filter are up to date. But once again, as we type another letter before the low priority render is finished, we then interrupt it again to cater to another high priority update. Also, in the second high priority render, as the previous low priority render wasn't able to finish, we're still seeing the first deferred filter value. This shows us that, in low priority renders, all values are up to date, regardless of their origin. However, in high priority renders, deferred values might be stale. This time, we're able to finish the low priority render before we type anything else. So as you can see, in the filtered list, the UI is updated accordingly. Actually, just let me go back, so you can see this happening. Like before, there's no filtering at all, because we never finished any low priority renders that update the list. And now, for the first time, we've finished the low priority render. And because we did that, you can see that the list actually updated. Going next, moving forward, we type a third letter, which, yet again, triggers a high priority render.

6. Synchronization of Deferred Values

Short description:

As long as low priority renders are interrupted, deferred values will be stale in high priority renders. The process repeats until we finish typing. Concurrent rendering requires identifying fast and slow updates and assigning priorities accordingly. Developers should check out a blog post for more details on concurrent rendering and a package for debugging concurrent features.

But now notice that, as we were able to finish the previous low priority render in this high priority render now, the deferred filter is up-to-date with the filter. They are synchronized, which tells us that, as long as low priority renders keep being interrupted, deferred values will be stale in high priority renders. And it's only when the low priority render is able to complete that states will be in sync again.

Now, the process repeats once more until we finish typing everything. So we type one more letter, and then we fill the list with low priority rendering. Maybe we finish it, maybe not, it gets interrupted until eventually we finish typing everything. Then the low priority renders are able to finish, and we get to the final state, so to speak.

Now, before we move forward, there are two important remarks that need to be made. First, in this example, we're using the use deferred value hook, but if we had used, like, the use transition hook instead, it would have worked pretty much the same way, because they work pretty much in the same way. Second, the more attentive ones might have noticed that even though this wasn't explicitly shown, due to the lack of screen real states, to be honest, the components whose updates are slow, namely, both the main component on the navigation example and the future list on the list filtering example, are being memoized. So there's a React memo wrapping their definitions, which is what makes their re-render be bypassed in high priority renders. So it's because their renders are bypassed, that we can keep the interface responsive by not having to re-render the expensive components, so to speak. And they are not re-rendered like the renders are bypassed, precisely because they receive a stale value in the high-priority renders.

Ok, so with all that in mind, now you might be thinking, so concurrent rendering is so nice that this means that we should use transitions and deferred values for everything, right? Right? Well, actually no. We definitely do not want to do that. If we do that, we would be doing the equivalent of assigning a low priority for each and every update. And if everything has a low priority, then there are no priorities at all, right? See, this is what we had pre-React 18, before concurrent rendering was a thing. Like, all updates had the same priority, which was the very cause of our problems in the first place. It was just that instead of every update having a low priority, they had a high priority. But it's the same thing, you know? So in the end, our job as developers is to identify which updates are fast, which are slow, and then assign priorities accordingly, so that we can, to the best of our efforts, keep the application responsive and deliver a great user experience. If you want to know more about Concurrent React, there's this blog post I wrote which goes into much more detail on concurrent rendering. It talks also about concurrent features, and even a little bit about suspense and how it interacts with the concurrent features. So don't forget to check it out. Also, last but not least, there's this tiny package I published that has a hook that helps you to debug concurrent features. It's the hook I used to create those logs you saw on the future list slide. It's pretty interesting. It's worth checking it out. So with all that being said, thank you so much and have a wonderful day.

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

React Advanced Conference 2022React Advanced Conference 2022
25 min
A Guide to React Rendering Behavior
Top Content
React is a library for "rendering" UI from components, but many users find themselves confused about how React rendering actually works. What do terms like "rendering", "reconciliation", "Fibers", and "committing" actually mean? When do renders happen? How does Context affect rendering, and how do libraries like Redux cause updates? In this talk, we'll clear up the confusion and provide a solid foundation for understanding when, why, and how React renders. We'll look at: - What "rendering" actually is - How React queues renders and the standard rendering behavior - How keys and component types are used in rendering - Techniques for optimizing render performance - How context usage affects rendering behavior| - How external libraries tie into React rendering
React Summit Remote Edition 2021React Summit Remote Edition 2021
33 min
Building Better Websites with Remix
Top Content
Remix is a new web framework from the creators of React Router that helps you build better, faster websites through a solid understanding of web fundamentals. Remix takes care of the heavy lifting like server rendering, code splitting, prefetching, and navigation and leaves you with the fun part: building something awesome!
React Advanced Conference 2023React Advanced Conference 2023
33 min
React Compiler - Understanding Idiomatic React (React Forget)
React provides a contract to developers- uphold certain rules, and React can efficiently and correctly update the UI. In this talk we'll explore these rules in depth, understanding the reasoning behind them and how they unlock new directions such as automatic memoization. 
React Advanced Conference 2022React Advanced Conference 2022
30 min
Using useEffect Effectively
Top Content
Can useEffect affect your codebase negatively? From fetching data to fighting with imperative APIs, side effects are one of the biggest sources of frustration in web app development. And let’s be honest, putting everything in useEffect hooks doesn’t help much. In this talk, we'll demystify the useEffect hook and get a better understanding of when (and when not) to use it, as well as discover how declarative effects can make effect management more maintainable in even the most complex React apps.
React Summit 2022React Summit 2022
20 min
Routing in React 18 and Beyond
Top Content
Concurrent React and Server Components are changing the way we think about routing, rendering, and fetching in web applications. Next.js recently shared part of its vision to help developers adopt these new React features and take advantage of the benefits they unlock.In this talk, we’ll explore the past, present and future of routing in front-end applications and discuss how new features in React and Next.js can help us architect more performant and feature-rich applications.
React Advanced Conference 2021React Advanced Conference 2021
27 min
(Easier) Interactive Data Visualization in React
Top Content
If you’re building a dashboard, analytics platform, or any web app where you need to give your users insight into their data, you need beautiful, custom, interactive data visualizations in your React app. But building visualizations hand with a low-level library like D3 can be a huge headache, involving lots of wheel-reinventing. In this talk, we’ll see how data viz development can get so much easier thanks to tools like Plot, a high-level dataviz library for quick & easy charting, and Observable, a reactive dataviz prototyping environment, both from the creator of D3. Through live coding examples we’ll explore how React refs let us delegate DOM manipulation for our data visualizations, and how Observable’s embedding functionality lets us easily repurpose community-built visualizations for our own data & use cases. By the end of this talk we’ll know how to get a beautiful, customized, interactive data visualization into our apps with a fraction of the time & effort!

Workshops on related topic

React Summit 2023React Summit 2023
170 min
React Performance Debugging Masterclass
Featured WorkshopFree
Ivan’s first attempts at performance debugging were chaotic. He would see a slow interaction, try a random optimization, see that it didn't help, and keep trying other optimizations until he found the right one (or gave up).
Back then, Ivan didn’t know how to use performance devtools well. He would do a recording in Chrome DevTools or React Profiler, poke around it, try clicking random things, and then close it in frustration a few minutes later. Now, Ivan knows exactly where and what to look for. And in this workshop, Ivan will teach you that too.
Here’s how this is going to work. We’ll take a slow app → debug it (using tools like Chrome DevTools, React Profiler, and why-did-you-render) → pinpoint the bottleneck → and then repeat, several times more. We won’t talk about the solutions (in 90% of the cases, it’s just the ol’ regular useMemo() or memo()). But we’ll talk about everything that comes before – and learn how to analyze any React performance problem, step by step.
(Note: This workshop is best suited for engineers who are already familiar with how useMemo() and memo() work – but want to get better at using the performance tools around React. Also, we’ll be covering interaction performance, not load speed, so you won’t hear a word about Lighthouse 🤐)
React Advanced Conference 2021React Advanced Conference 2021
132 min
Concurrent Rendering Adventures in React 18
Top Content
Featured WorkshopFree
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.
React Summit Remote Edition 2021React Summit Remote Edition 2021
177 min
React Hooks Tips Only the Pros Know
Top Content
Featured Workshop
The addition of the hooks API to React was quite a major change. Before hooks most components had to be class based. Now, with hooks, these are often much simpler functional components. Hooks can be really simple to use. Almost deceptively simple. Because there are still plenty of ways you can mess up with hooks. And it often turns out there are many ways where you can improve your components a better understanding of how each React hook can be used.You will learn all about the pros and cons of the various hooks. You will learn when to use useState() versus useReducer(). We will look at using useContext() efficiently. You will see when to use useLayoutEffect() and when useEffect() is better.
React Advanced Conference 2021React Advanced Conference 2021
174 min
React, TypeScript, and TDD
Top Content
Featured WorkshopFree
ReactJS is wildly popular and thus wildly supported. TypeScript is increasingly popular, and thus increasingly supported.

The two together? Not as much. Given that they both change quickly, it's hard to find accurate learning materials.

React+TypeScript, with JetBrains IDEs? That three-part combination is the topic of this series. We'll show a little about a lot. Meaning, the key steps to getting productive, in the IDE, for React projects using TypeScript. Along the way we'll show test-driven development and emphasize tips-and-tricks in the IDE.
React Advanced Conference 2021React Advanced Conference 2021
145 min
Web3 Workshop - Building Your First Dapp
Top Content
Featured WorkshopFree
In this workshop, you'll learn how to build your first full stack dapp on the Ethereum blockchain, reading and writing data to the network, and connecting a front end application to the contract you've deployed. By the end of the workshop, you'll understand how to set up a full stack development environment, run a local node, and interact with any smart contract using React, HardHat, and Ethers.js.
React Summit 2023React Summit 2023
151 min
Designing Effective Tests With React Testing Library
Featured Workshop
React Testing Library is a great framework for React component tests because there are a lot of questions it answers for you, so you don’t need to worry about those questions. But that doesn’t mean testing is easy. There are still a lot of questions you have to figure out for yourself: How many component tests should you write vs end-to-end tests or lower-level unit tests? How can you test a certain line of code that is tricky to test? And what in the world are you supposed to do about that persistent act() warning?
In this three-hour workshop we’ll introduce React Testing Library along with a mental model for how to think about designing your component tests. This mental model will help you see how to test each bit of logic, whether or not to mock dependencies, and will help improve the design of your components. You’ll walk away with the tools, techniques, and principles you need to implement low-cost, high-value component tests.
Table of contents- The different kinds of React application tests, and where component tests fit in- A mental model for thinking about the inputs and outputs of the components you test- Options for selecting DOM elements to verify and interact with them- The value of mocks and why they shouldn’t be avoided- The challenges with asynchrony in RTL tests and how to handle them
Prerequisites- Familiarity with building applications with React- Basic experience writing automated tests with Jest or another unit testing framework- You do not need any experience with React Testing Library- Machine setup: Node LTS, Yarn