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:
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 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
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
CarDetails. Note that I am using an arbitrary API, just to show you how
Suspense requires library support, and cannot yet work with
fetch directly. Facebook’s GraphQL client, Relay, supports
Suspense right now.
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
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
Thank you for reading, I hope you are as exited for Concurrent Mode as I am. Stay tuned for more articles!