November 4, 2019











React is constantly expanding, and with that process, there are a few cool features coming. One of them is Concurrent Mode. In this article, we go through its principles and discuss its current state. We also look at what the future might bring.

The first thing to ask would be: what is the Concurrent Mode? It is a whole set of features that aim to increase the performance of React applications. Its state is still experimental, but it is getting closer and closer to being production-ready. It changes enough under the hood of React that it is worth to talk about it.

The purpose of the Concurrent Mode

The main reason for the Concurrent Mode to exist is to increase the performance of React applications. While it can’t make our JavaScript code run faster, it makes some changes to the rendering process.

We talk a bit about how it works in the Algorithm React uses to update the view. Increasing the performance of a component

To understand it, let’s consider a filterable list. If you make it in a way that on every keypress it rerenders the view, it might lack in terms of performance. The above is because once the rendering process starts, it can’t be interrupted. When we render the list of items, nothing else on the page can happen until React finishes the process – or at least it used to be liked that.

To illustrate it better, let’s consider this simple example.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import React , { Component } from 'react' ; import comments from './comments' ; import Comment from './Comment' ; import styles from './styles.module.css' ; class List extends Component { state = { filteredContent : '' } handleInputChange = ( event ) = > { this . setState ( { filteredContent : event . target . value } ) } getFilteredComments ( ) { const { filteredContent } = this . state ; return filteredContent ? comments . filter ( comment = > { return comment . body . includes ( filteredContent ) ; } ) : comments ; } render ( ) { return ( < div className = { styles . list } > < input onChange = { this . handleInputChange } value = { this . state . filteredContent } placeholder = "Filter the comments by content" / > { this . getFilteredComments ( ) . map ( comment = > ( < Comment key = { comment . id } author = { comment . email } content = { comment . body } / > ) ) } < / div > ) } } export default List ;

In the above component, we have a long list of comments taken from the jsonplaceholder.typicode.com site. Every time we start the filtering process by typing in the input, React begins a rendering process. It might take some time due to a significant number of elements to render. When the user presses a key again to type an additional character, there might be a visible stutter. If the JavaScript thread is busy rendering a new list of items, it can’t update the input.

Current state the Concurrent Mode

Let’s make the rendering process concurrent. Thanks to that, the above rendering process can be stopped to react to the user typing in the input first. The above is due to the fact that Concurrent Mode works by assigning priority to different actions based on their importance. Thanks to that, an update with a higher priority can interrupt the low priority work even if it already started. This concept is called Time Slicing. With it, our components can be more responsive, even with a lot of stuff going on within the page.

We’ve built a generic way to ensure that high-priority updates like user input don’t get blocked by rendering low-priority updates. Dan Abramov – Beyond React 16

To do it, we need to install an experimental version of React:

1 npm install react @ experimental react - dom @ experimental

You can do the above with the Create React App script

As of now, there are three modes in which we can run our React applications:

Legacy

The stable version of React currently operates in the legacy mode. It is not able to support the new features.

1 ReactDOM . render ( < App / > , document . getElementById ( 'root' ) )

The stable version of React currently operates in the legacy mode. It is not able to support the new features. Blocking

Currently experimental. Acts as a step that we can take in our application to get closer to the Concurrent Mode. It does not include all of the features.

1 ReactDOM . createBlockingRoot ( document . getElementById ( 'root' ) ) . render ( < App / > )

Currently experimental. Acts as a step that we can take in our application to get closer to the Concurrent Mode. It does not include all of the features. Concurrent Mode

It is also experimental and contains all of the newest features. The React team intends to make it a default mode when it stabilizes.

1 ReactDOM . createRoot ( document . getElementById ( 'root' ) ) . render ( < App / > ) ;

When we turn the Concurrent Mode on in our application that filters comments, we can experience a bit of a performance boost. The different priorities that Concurrent Mode uses internally aim to meet the case of human interaction. Page transitions and clicks can wait a little bit longer than a hover or changing a text input value.

The interesting fact is that Facebook already uses Concurrent Mode in production! Not everywhere, though. Their code still contains unsafe lifecycle methods. Unfortunately, using Concurrent Mode makes them even more “unsafe”. It might be a good idea to use the strict mode if you want to experiment with the newest React features.

If you want to know more, check out Bug-proofing our application with Error Boundaries and the Strict Mode

Diving deeper into Concurrent Mode features

A big part of the Concurrent Mode specification is the Suspense. It is a mechanism that lets us wait for a component to render fully. With it, various libraries – for example, data fetching ones – can communicate to React that the component is not ready yet. The above creates a way for components to suspend rendering while they load asynchronous data. Let’s rewrite our List component in a way that makes an actual API request so that we might wait for it.

For React to understand that a component that makes an API request is not yet fully rendered, it needs to read a resource. They are a kind of wrapper around promises, and we don’t have with any reliable way of creating them right now. We can do it ourselves, though!

I tried using the npm-cache package that exports the unstable_createResource function, but without success

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 const commentsResource = wrapPromise ( fetch ( 'https://jsonplaceholder.typicode.com/comments' ) . then ( response = > response . json ( ) ) ) ; function wrapPromise ( promise ) { let status = 'pending' ; let result ; let suspender = promise . then ( response = > { status = 'success' ; result = response ; } , e = > { status = 'error' ; result = e ; } ) ; return { read ( ) { if ( status === 'pending' ) { throw suspender ; } else if ( status === 'error' ) { throw result ; } else if ( status === 'success' ) { return result ; } } } ; }

Now, we can use our resource in the List component.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 import React , { Component } from 'react' ; import Comment from './Comment' ; import styles from './styles.module.css' ; import commentsResource from './commentsResource' ; class List extends Component { state = { filteredContent : '' } handleInputChange = ( event ) = > { this . setState ( { filteredContent : event . target . value } ) } getFilteredComments ( comments ) { const { filteredContent } = this . state ; return filteredContent ? comments . filter ( comment = > { return comment . body . includes ( filteredContent ) ; } ) : comments ; } render ( ) { const comments = commentsResource . read ( ) ; return ( < div className = { styles . list } > < input onChange = { this . handleInputChange } value = { this . state . filteredContent } placeholder = "Filter the comments by content" / > { this . getFilteredComments ( comments ) . map ( comment = > ( < Comment key = { comment . id } author = { comment . email } content = { comment . body } / > ) ) } < / div > ) } } export default List ;

Please note that we call read inside of the render function. Now we can use the new Suspense component to wrap the List.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import React , { Suspense } from 'react' ; import List from './List' ; import Loader from './Loader' ; function App ( ) { return ( < Suspense fallback = { < Loader / > } > < List /> </S uspense > ) ; } export default App ;

1 2 3 const Loader = ( ) = > 'Loading' ; export default Loader ;

In the simple example above, we display the Loading text before the List component is ready.

Another good example of Suspense use is with the React lazy components. It’s been added to React in version 16.6, so it is stable already!

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import React , { Suspense } from 'react' ; import Loader from './Loader' ; const List = React . lazy ( ( ) = > import ( './List' ) ) ; function App ( ) { return ( < Suspense fallback = { < Loader / > } > < List /> </S uspense > ) ; } export default App ;

The Relay framework has been successfully used in production with the use of the Suspense

The React team intends to make Suspense the primary way of reading asynchronous data from within components. React docs contain even more examples of the usage of the Concurrent Mode and Suspense. Feel free to check it out!

Summary

The Concurrent Mode is not a solution for all of the performance issues out there. Our code does not only updates the DOM tree. While it can’t make our JavaScript code run faster, it seems like an exciting addition to React. It is in development for quite some time, and there is still work before marking it as stable.

I look forward to experimenting with Concurrent Mode even more. What are your thoughts on it?