What is React Concurrent Mode?

Sveta Slepner
The Startup
Published in
8 min readApr 3, 2020

For the past three years, the React core team has been working on a major feature that is going to significantly affect both user experience and development process: Concurrent Mode. While work is still in progress and most of its parts are not official yet, let’s dive in and see what awaits us in the near future.

What is React Concurrent Mode?

First, What Does “Concurrency” Even Mean?

As you probably know, Javascript is a single-threaded language. Each task you run blocks the thread from executing the next one until it’s done. However, it doesn’t mean we can’t make progress on more than one task at the same time. Confused? Let me try and explain it using a real-life example.

Say you’re a morning person and can’t start the day without a cup of tea and a raspberry jam spread toast.

If we go on with what we said earlier, it will mean we have to make the tea, and only when it’s done, prepare the toast.

Tea->Toast

But it’s not very smart, is it? We do want that tea hot in the end.

So to be efficient and get both tea and toast hot and fresh, we should instead break both tasks into smaller micro-tasks, and reorder them as we see fit:

Concurrency IRL

Tada! While being a single person handling 2 different tasks, we were able to make progress on two tasks “at the same time”.

So what is Concurrency? It is a way to structure a program by breaking it into pieces that can be executed independently. This is how we can break the limits of using a single thread, and make our application more efficient.

So now that we know the definition, let’s take a look at what this has to do with React.

It’s All About Perfecting the User Experience

The browser’s UI thread is responsible for applying all the changes initiated by CSS, User input and Javascript to the user’s screen. On modern machines, to provide the best user experience, we are expected to render 60 frames per second (fps). To achieve that, for each render cycle our code should run for no more than 16.67 milliseconds, and realistically even less: somewhere around 10 ms (since, as said previously, the browser has also to deal with other UI tasks).

React is javascript and thus bound by the same limits. Up until now, once React started the reconciliation phase, it could no longer be stopped until it’s done. The browser’s main UI thread would then be unable to execute any other tasks, such as receiving user input. Even if React’s reconciliation algorithm is amazingly efficient — when a web application grows big and its dom tree grows, it’s easy to understand how causing frame drops which lead to jank or even unresponsive applications is a common problem.

Here’s a working example of an app that renders a grid of 100X100 randomly colored pixels on every keypress. Try typing something down:

Blocking Rendering

Annoying, isn’t it? It might be a bit of an exaggeration, rendering 10,000 dom nodes, but it’s just a quick way to demonstrate the problem.

And while most developers will use techniques, such as memoization and debounce, to make the experience feel better, they will just delay the main problem: rendering is still a truck blocking the road.

But improving user experience is not all about fixing performance problems, but also thinking about what users will consider a better experience.

Spinners, skeletons, placeholders — you can’t run away from them whether you have a high-end machine or a low budget phone. While they provide a good indication that something is going on, when you put too many of them together or show them unnecessarily, it degrades the experience instead of improving it. If the data can load fast enough on your machine, why do you have to see these spinners in the first place? Wouldn’t it be a nicer experience to let you wait for an extra second and move you right on to a fully rendered and ready page?

Interpretive Rendering To the Rescue

Remember what I told you about how concurrency is all about breaking tasks to smaller pieces, which allows us to progress on multiple tasks? This is exactly what React now does: the rendering process is being broken down into smaller tasks, and a scheduler allows us to even prioritize them based on importance (also called “time-slicing”). This lets Concurrent React:

  • Not to block the main thread;
  • To work on multiple tasks at a time and switch between them according to priority;
  • To partially render a tree without committing the result.

The rendering process is no longer blocking the thread, it’s interpretative and can be put aside if something of higher importance comes along, such as a user pressing a key.

If you’re interested in the nitty-gritty and how does React Fiber (the new reconciliation algorithm) does that, I highly recommend you to watch this video by Lin Clark.

So now let’s put the theory in use, with some of the concurrent mode’s new features!

But a quick note before that: as said, the following features are not yet part of the official React library. They might change in the future, so it’s not advised to use them in production. Also, to enable them you’ll need to:

A. Use the ‘experimental’ package of react and react-dom.
B. Change your initial render call to:

// Concurrent Mode
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);

useDeferredValue

Remember the demo from earlier, where the UI would get stuck on every keypress? To fix the user experience, we need a way to prioritize user inputs, and only then care about rendering the huge grid. Luckily we now have a way to do so: useDeferredValue.

useDeferredValue is a hook that wraps a prop/state value and receives the maximum defer time. This lets us tell React: “It’s ok for components depending on this value to be rendered at a later time”.

import { useState, useDeferredValue } from 'react';const [value, setValue] = useState('');
const deferredValue = useDeferredValue(value, {
timeoutMs: 5000
});

Note that this functionality does not debounce the render! React will still render the components “on the side”, and, if ready before the provided time, it will flush the changes to the DOM.

Note: Using this feature might cause an inconsistency in the view since we’re prioritizing showing one piece of the UI over the other.

Blocking VS Interruptible rendering

Suspense for Data Fetching

Another awesome new feature (and the one I’m personally most excited about) is Suspense for data fetching. If you’re familiar with React’s newest features, you might already know about Suspense for data fetching, which comes hand to hand with React.lazy introduced in version 16.8. Suspense allows us to show a placeholder while waiting for a code split part of our application.

Suspense for Data fetching has the same idea behind it, but it works on basically anything using promises.

import Spinner from './Spinner';<Suspense fallback={<Spinner />}>
<SomeComponent />
</Suspense>

To see how powerful it is, let’s look at a simple application without Suspense:

No Suspense

This application loads a list of tv shows. When pressing on a tv show, it’ll load a details page with a comments section.

const [tvData, setTvData] = useState(null);useEffect(()=>{
setTvData(null);
tvDataApi(id).then(value =>{
setTvData(value);
})
}, [id]);
if (tvData === null) return <Spinner />;return <div className="tvshow-details">
<div className="flex">
<div>
<h2>{tvData.name}</h2>
<div className="details">{tvData.description}</div>
</div>
<div>
<img src={`${id}.jpg`} alt={tvData.name} />
</div>
</div>
{/* comments section */
<Comments id={id} />
</div>

If we look closely at the way we load the data here, we can see that:
A. There’s currently no unified way to display a placeholder while the data is loading. Each developer has to implement it for himself.
B. We create a waterfall of API calls since we can only render the comments component when details are ready and rendered. There are other ways to handle this of course, but none are simple or intuitive.

Suspense solves both since it now provides a simple syntax for displaying placeholders. Also, using the concurrent mode features, it can actually start rendering the component on the side, thus initiating the API calls, and show the placeholder in the meanwhile. This is a huge feature which takes away a lot of pain when working with data.

export const TvShowDetails = ({ id }) => {
return (
<div className="tvshow-details">
<Suspense fallback={<Spinner />}>
<Details id={id} />
<Suspense fallback={<Spinner />}>
<Comments id={id} />
</Suspense>
</Suspense>
</div>
);
};

To work with Suspense for data fetching, we need to wrap our promises with a function (see wrapPromise in the demo below), which returns different values based on what Suspense is expecting on each step of the process. The React team is working on a library called react-cache, which will provide this function, but nothing is final yet.

Suspense

Anyhow, using this syntax simplifies our component. We can dropuseEffect and no longer worry about what will happen if the data is not ready. We can treat the component as if the data is there, and let Suspense do the rest.

const detailsData = detailsResource(id).read();return <div>{detailsData.name} | {detailsData.score}</div>

But now we are facing another problem. What if the API call inside the comments component finishes first? It would look weird to show it first, right? Thankfully, we now have a way to coordinate the view order of components using Suspense.

SuspenseList

SuspenseList is a component we can wrap other Suspense components with. it receives two props: revealOrder and an optional tail prop, with which we can tell react the order to display the child Suspense wrapped components (See the documentation for all the options).

const [id, setId] = useState(1);<SuspenseList revealOrder="forwards">
<Suspense fallback={<Spinner />}>
<Details id={id} />
</Suspense>
<Suspense fallback={<Spinner />}>
<Comments id={id} />
</Suspense>
</SuspenseList>

Note that it has nothing to do with rendering or API order call, it simply tells react when to display a loaded component.

useTransition

We previously talked about spinners, and how a better experience would be to maybe not display them at all, for users with a fast internet connection.

We can now do that with a new hook called useTransition.
useTransition
lets us wait for the data to be ready before flushing the dom, so basically we see nothing until the change actually happens. It receives the amount of time we want to wait before falling back to a spinner or some other fallback component.

It also returns two things:

startTransition — A function that tells React which state update we want to defer.

isPending — A boolean which tells us if a transition is ongoing at the moment.

const [id, setId] = useState(1);const [startTransition, isPending] = useTransition({ timeoutMs: 3000 });const onClick = id => {
startTransition(() => {
setId(id);
});
};

Naturally, using this hook comes hand-to-hand with using Suspense for data fetching.

See the demo here:

useTransition

As you can see, React’s concurrent mode is packed with awesome features which both takes away the headache of common use-cases, and also lets us create a better user experience for our users. There is no official release date, but hopefully, we won’t have to wait too long!

Some extra reading:

--

--