22 September 2021
Concurrent rendering. Let’s take a closer look at React 18 concurrent mode
After React 17, which btw. has left some of us a bit dissatisfied, here comes something we’ve actually been waiting for. It’s React 18, full of performance features, most importantly new concurrent rendering possibilities!
Although the previous release didn’t add any groundbreaking changes, it was very important for future versions. Now, it’s possible to use methods from multiple React versions in the same application. Gradual upgrades have great potential and allow you to adapt your code step by step.
Before we jump into the practical details of React 18 concurrent mode, let’s do a quick overview of relevant novelties.
React Working Group
React 18 alpha version presentation was preceded by the introduction of React Working Group. It’s a space where the community can ask questions to library authors and provide feedback about new features. Discussions are taking place at Github Discussions which are public and available for everyone, so don’t hesitate to participate.
New root API
React 18 ships a new root API, but as you probably suspect the old one is still available. So now we have two ways two initialize the app:
- Legacy root API, which you know well and can be called with ReactDOM.render:
- New root API, which can be called with ReactDOM.createRoot. Only this API opens the access for new concurrent features and adds out-of-the-box improvements:
At the first glance, there is no reason to pass container at initial render, but there are two main differences:
- There is no possibility to add a callback to render as in legacy root API. React maintainance advises using other methods instead, e.g. ref, requestIdleCallback, or setTimeout. The reason is partial hydration and progressive SSR that affects difficulties in timing properly.
- The hydration function was moved to hydrateRoot API.
💡 I’m not going to go through every single new React 18 function. We’ve already covered that in our constantly updated React latest version article. I encourage you to go there for more details.
What is Concurrent Rendering?
React 18 brings a new concurrency model. To provide concurrency React uses cooperative multitasking, priority-based rendering, scheduling, and interruptions. Also, tasks can now overlap.
SSR (Streaming Server Render)
It’s probably the most important concurrent feature, React 18 supports server-side rendering. And SSR makes rendering content on the server possible.
When a page is built with JavaScript, it must be downloaded first and then executed. It’s very profitable for a great user experience to show a fully interactive page as soon as possible. However, without SSR it’s not possible. The only thing that the user will see until JavaScript will be proceeded is a blank page. SSR is a perfect solution for this issue since it lets you render React components on the server and then send them to the user’s browser as HTML. Although HTML won’t allow to fully interact with the application, it will allow showing something more than a blank page while JavaScript is loading.
The last step is hydration, which is just completing the whole. What is hydration in software development (because obviously, I don’t mean your mandatory three litres of water per day)? Hydration connects JS with static HTML generated on the server. Now users can fully interact with your application.
It may seem that problem’s solved. Unfortunately, there are still some important issues to tackle. On my list, I have three areas:
- time for load all HTML,
- time for load all JS to make the component interactive,
- time for all components to hydrate to interact with one component.
New Suspense to the rescue
React has started sort of supporting Suspense from version 16 but it was never A FULL support. Now, Suspense works in the way described above – it makes hydrations a one or zero switch. The application must fetch code and hydrate at once.
React 18 comes with some interesting changes in this area. You can divide your app into smaller and independent parts that won’t block the rest of the app during loading.
The new suspense component allows you to wait for some code and show a fallback component before the code is loaded. This is possible due to two new mechanisms.
Streaming HTML
Streaming HTML is just sending lightweight static HTML. It’s replaced by heavier components that depend, for example, on database data.
You can wrap some parts of your code and tell which critical components will take a bit longer to load. When you wrap <Comments /> with <Suspense /> you are basically telling React not to wait for this component in order to start streaming HTML. Now, after a user enters a page with an interesting article, they will see the loader. It’s very possible that comments data will be fetched and replaced before the user ends reading the article and scroll down to the comments section.
Selective hydration
Selective hydration mechanisms decide which components need to be loaded first. React detects events started by the user’s interaction with the component and hydrate certain components first.
If you have multiple Suspense, React will attempt to hydrate all of them but it will start with Suspense found earlier in the tree first.
Going back to our example, let’s say it was extended with a recommendation section under the comments. Now, when a user clicks on recommendations, React will prioritize hydration of this component and it will be loaded before comments are.
startTransition API
The new startTransition method allows to group changes that require a complicated re-render and execute them without blocking the user’s interaction with the application. To be clear, if the start transition is called again before the previous call is completed, React by default cancels older transitions.
Let’s take a look at this example of text input, filtering a list of elements. You probably want to see value in input immediately, while results in the search list can be shown a moment later. You can mark the input update as urgent and filter it as a slower update (transition).
React provides a hook for transition with isPending, so you can inform about work in the background with e.g. loader. 🙂
useDefferedValue
This hook returns a deferred value that can be lagged at a certain time. It’s an awesome solution, for cases like lists with fetched data that depends on the user’s input value. This allows making a timeout before data is fetched. The new input value is shown immediately, while the list will be lagged.
Automatic batching
Batching allows you to group multiple state updates into a single re-render. However, it has already been present in React but…. it hasn’t really. On one hand, updates during React event handlers were batched but on the other, updates inside other contexts like promises, setTimeouts, or native event handlers were not batched.
In React 18 automatic batching in updates for all use cases will be applied by default. You only need to use the new root API.
What if I don’t need to batch state updates? – you ask.
There might be cases when one update is required before another one. Don’t worry, there is flushSync method that allows you to disable batching as you wish. So it’s there but you don’t need to use it.
If you use Class components, there’s a good edge case you might wanna know about
When using multiple setState updates inside events, you are able to read synchronously this.setState value between calls. It won’t render the result of the first update in the same way, including automatic batching. If this is a side effect for you, feel free to wrap the first update in flushSync and be able to read again the value of the first update until the next one happens.
TA-DA! Now you kill some time by playing with our interactive panel
Will React 18 bring anything revolutionary after React 17 lulled our vigilance?
Getting to the point – it’s absolutely worth following the news related to React 18, and at the same time, wait for the concurrent rendering mode itself!
If there’s any React revolution to televise, it will probably be the concurrent rendering.
Concurrent rendering benefits
Above all, the concurrent mode will give significant room for improving performance, and therefore the user experience.
But the concurrent mode in React 18 will also help in:
- limiting re-renders,
- prioritising states updates,
- implementing new server-side rendering architecture (SSR).
In every application, speed and user-friendliness have always been the most important goals. Looking at concurrent rendering, and its features, we are a step further to speed up our apps and move to a higher level of user satisfaction.
Bonus: How to install the latest React 18?
Last but not least! Are you looking forward to starting your work with React 18?
For now, it is worth installing React 18, but only experimentally. Due to the fact that this is still the Alpha version, many things still require refinement and final polishing. Therefore, React 18 is not yet suitable for production use. It’s not a flaw or anything strange, mind. It’s a normal software development process. But you can (and should) still check it out!
All you need to use is the alpha tag.
If you use Typescript be sure to include new types in compilerOptions section of your tsconfig.json.
Hope you have a good time with it! 😃