React Concurrent Mode

What Is Concurrent Mode?

Concurrent Mode is a set of new features that help React apps stay responsive and gracefully adjust to the user’s device capabilities and network speed.

These features are still experimental and are subject to change. They are not yet a part of a stable React release, but you can try them in an experimental build.

Blocking vs Interruptible Rendering

Once React starts rendering an update, including creating new DOM nodes and running the code inside components, it can’t interrupt this work. This approach is called “blocking rendering”.

In Concurrent Mode, rendering is not blocking. It is interruptible. This improves the user experience. It also unlocks new features that weren’t possible before.

Interruptible Rendering

Consider a filterable product list. Have you ever typed into a list filter and felt that it stutters on every key press? Some of the work to update the product list might be unavoidable, such as creating new DOM nodes or the browser performing layout. However, when and how we perform that work plays a big role.

The reason for the stutter is simple: once rendering begins, it can’t be interrupted. So the browser can’t update the text input right after the key press. No matter how good a UI library (such as React) might look on a benchmark, if it uses blocking rendering, a certain amount of work in your components will always cause stutter. And, often, there is no easy fix.

Concurrent Mode fixes this fundamental limitation by making rendering interruptible. This means when the user presses another key, React doesn’t need to block the browser from updating the text input. Instead, it can let the browser paint an update to the input, and then continue rendering the updated list in memory. When the rendering is finished, React updates the DOM, and changes are reflected on the screen.

To explain Concurrent Mode, we’ll use version control as a metaphor. Conceptually, you can think of this as React preparing every update “on a branch”. Just like you can abandon work in branches or switch between them, React in Concurrent Mode can interrupt an ongoing update to do something more important, and then come back to what it was doing earlier.

Concurrent Mode techniques reduce the need for debouncing and throttling in UI. Because rendering is interruptible, React doesn’t need to artificially delay work to avoid stutter. It can start rendering right away, but interrupt this work when needed to keep the app responsive.

Intentional Loading Sequences

Post-concurrent mode we let react's engine interrupt components and in combination with suspense know our dependencies with external services so it can smartly make use of this information and decide what it's more convenient to render at each time.

An example, very skewed to the suspense feature. We have a section on our screen that is going to show a spinner while fetching data from an API. This API only needs 20ms to return a value. In synchronous React, we are going to see a spinner for a small fraction of time which turns out to be very "inefficient" because after the browser renders that It has to recalculate the new layout with the result of the endpoint.

It would be way more convenient to wait a couple of milliseconds more to make efficient use of the resources and also present to the user a final state of the UI instead of the, sometimes very annoying, intermediate state.

While this is possible today, it can be difficult to orchestrate. In Concurrent Mode, this feature is built-in. React starts preparing the new screen in memory first — or, as our metaphor goes, “on a different branch”. So React can wait before updating the DOM so that more content can load. In Concurrent Mode, we can tell React to keep showing the old screen, fully interactive, with an inline loading indicator. And when the new screen is ready, React can take us to it.

Concurrency

Let’s recap the two examples above and see how Concurrent Mode unifies them. In Concurrent Mode, React can work on several state updates concurrently — just like branches let different team members work independently:

  • For CPU-bound updates (such as creating DOM nodes and running component code), concurrency means that a more urgent update can “interrupt” rendering that has already started.

  • For IO-bound updates (such as fetching code or data from the network), concurrency means that React can start rendering in memory even before all the data arrives, and skip showing jarring empty loading states.

Importantly, the way you use React is the same. Concepts like components, props, and state fundamentally work the same way. When you want to update the screen, you set the state.

React uses a heuristic to decide how “urgent” an update is, and lets you adjust it with a few lines of code so that you can achieve the desired user experience for every interaction.

Suspense for Data Fetching

Suspense for Data Fetching is a new feature that lets you also use <Suspense> to declaratively “wait” for anything else, including data.

Suspense is not a data fetching library. It’s a mechanism for data fetching libraries to communicate to React that the data a component is reading is not ready yet. React can then wait for it to be ready and update the UI. At Facebook, we use Relay and its new Suspense integration. We expect that other libraries like Apollo can provide similar integrations.

What Suspense Lets You Do

So what’s the point of Suspense? There are a few ways we can answer this:

  • It lets data fetching libraries deeply integrate with React. If a data fetching library implements Suspense support, using it from React components feels very natural.

  • It lets you orchestrate intentionally designed loading states. It doesn’t say how the data is fetched, but it lets you closely control the visual loading sequence of your app.

  • It helps you avoid race conditions. Even with await, asynchronous code is often error-prone. Suspense feels more like reading data synchronously — as if it were already loaded.

Traditional Approaches vs Suspense

We could introduce Suspense without mentioning the popular data fetching approaches. However, this makes it more difficult to see which problems Suspense solves, why these problems are worth solving, and how Suspense is different from the existing solutions.

Approaches of rendering:

  • Fetch-on-render (for example, fetch in useEffect): Start rendering components. Each of these components may trigger data fetching in their effects and lifecycle methods. This approach often leads to “waterfalls”.

  • Fetch-then-render (for example, Relay without Suspense): Start fetching all the data for the next screen as early as possible. When the data is ready, render the new screen. We can’t do anything until the data arrives.

  • Render-as-you-fetch (for example, Relay with Suspense): Start fetching all the required data for the next screen as early as possible, and start rendering the new screen immediately — before we get a network response. As data streams in, React retries rendering components that still need data until they’re all ready.

useTransition and useDeferredValue

useTransition allows components to avoid undesirable loading states by waiting for content to load before transitioning to the next screen. It also allows components to defer slower, data-fetching updates until subsequent renders so that more crucial updates can be rendered immediately. (wait for some time before showing spinner)

useDeferredValue returns a deferred version of the value that may “lag behind” it for at most timeoutMs. This is commonly used to keep the interface responsive when you have something that renders immediately based on user input and something that needs to wait for a data fetch. (stay on the current screen and after data is ready move to the next one)

Last updated

Was this helpful?