Everything You Need to Know About React Concurrent Mode in 2020

React evolves constantly, and you have to keep up to be valuable on the market. In this post, I will tell you about React Concurrent Mode, a new feature that has yet to be adopted.

Under no circumstances use this advice for production (as of May 2020). Concurrent Mode is experimental, full of bugs, and may or may not introduce breaking changes while you are reading this article.

What is Concurrent Mode?

React has a complex mechanism under the hood to handle updates to components. Concurrent Mode is a set of changes to this algorithm, to essentially let React render multiple trees at the same time, interrupt, delay, or dismiss an ongoing render and defer state updates.

You can think of it as a version control system: at any point in time, user is seeing the “master” branch. React, in the background, renders different branches. Once it is time to present it to user, they are merged into “master”. If a particular branch is no longer required, it is torn down and never merged at all.

Why do we need Concurrent Mode?

Mainly, for user experience. I think most of you experienced stuttering and weird freezes in your React applications. In particular, you can notice it while typing in an autocomplete, asynchronous text field (search suggestions). There are many ways around this problem, but none of them solved it until now.

This diagram should make it clearer. Once the API requests come back with autocomplete data (which is no longer valid), the UI is blocked updating it. With React Concurrent Mode, the UI is never blocked by a synchronous action. As soon as you change a value in the text field, React stops doing whatever it is it was doing and starts propagating these changes immediately, without waiting for that old autocomplete call and render to complete.

Of course, this particular problem can be solved by debouncing and throttling. But these are not very elegant and would still result in stuttering on low network speeds. React Concurrent Mode is a comprehensive solution for all similar problems, in all environments.

Most importantly, render can be interrupted halfway, if more relevant data has arrived. This should result in an unprecedented performance increase.

How do you use Concurrent Mode?

Again, I cannot stress this enough, do not use it on sensitive, production projects. Even though these features are somewhat stable right now, the development team may change them completely at any time.

Right now, Concurrent Mode is only implemented for ReactDOM (conventional HTML React), not React Native. You can use it in React Native only in web builds, by using a custom entry script. For regular React, this script is already included. You need to change the line ReactDOM.render(<App />, rootNode) to:

ReactDOM.createRoot(rootNode).render(<App />)

That is it. This will enable full Concurrent Mode for your application. You should not notice any immediate changes, however. The only thing that can happen is your app will no longer work, if you are using third-party dependencies which are incompatible with Concurrent Mode.

Now, by itself Concurrent Mode is almost useless. To use, we will refer to 2 more features React rolls out together with Concurrent Mode: Suspense and useTransition.

Suspense

Suspense is not only a way to speed up your application, but it also makes it simpler. Suspense lets you wait for components to render fully, and display a loader while it is rendering. Consider this example:

On line 3 we start loading cars from remote server, assuming this call might take a while. Then, App renders, and Suspense tries to render CarDetails. CarDetails calls cars.read(), which is blocked until cars are loaded. Suspense sees that the component cannot yet render and renders fallback instead. As soon as car data is ready, Suspense will replace fallback with CarDetails. Note that I am using an arbitrary API, just to show you how Suspense works. Suspense requires library support, and cannot yet work with fetch directly. Facebook’s GraphQL client, Relay, supports Suspense right now.

useTransition

useTransition deals closely with Suspense, but lets you have more control over the process. In short, useTransition is a hook that lets you delay state updates if the data is not there yet. For example, after pressing a button to navigate to next screen, you might want to wait for a second so the next page can load, without user seeing any white screens or loading spinners. useTransition is used like this:

Again, we are using an arbitrary API implementation, very close to Relay. Initially, initialResource is used, which has no cars. User can load cars by pressing the button. The stare will be updated to use carResource, and CarView will have some cars to display. But, they have to be loaded first.

This is where useTransition comes into play. It returns 2 values: a startTransition, which is a function that starts the transition, and isPending, which is a boolean to let you know if the transition is in progress. In the config object that is passed to useTransition, we specify the timeoutMs property. This is the maximum time useTransition can wait to delay the state update. If after 1 second the data is still not there, the state will be updated regardless and CarView will rerender on the main screen.

On line 9 you can see usage of startTransition. We pass in a function that updates the state to use carResource. React will make a copy of the CarView, pass in the updated resource and wait for it to rerender. If data is fetched within one second, CarView will finish rendering and React will merge in into main UI tree. If it is not there after 1 second, React will still merge it, so you will need to implement a loading spinner inside CarView yourself.

Closing notes

Thank you for reading, I hope you are as exited for Concurrent Mode as I am. Stay tuned for more articles!

Resources

Get new content delivered to your mailbox:

leave a comment