Experimental React - Concurrent Mode is Coming
React is an open-source JavaScript library developers use to build interactive user interfaces and UI components for web and mobile-based applications. Created by Jordan Walke, a Facebook Software engineer, the first version of React came out seven years ago. Now, Facebook maintains it. Since its initial release, React’s popularity has skyrocketed.
In October 2020, React 17 was announced with — surprisingly — zero new features. However, this certainly doesn’t mean nothing exciting was added. In fact, this release comes with many promising upgrades, including several improvements and bug fixes to version 16’s experimental features: Concurrent Mode and Suspense.
Though not yet available to the broader public, these features are available for developer experimentation. If and when released, they are set to change the way React renders its UI, improving both performance and user experience.
Very briefly, the Concurrent Mode and Suspense features enable users to handle data loading, loading states, and the user interface more fluidly and seamlessly. In Concurrent Mode, React can pause expensive, non-urgent components from rendering and focus on more immediate or urgent behaviors like UI rendering and keeping the application responsive.
This article takes a comprehensive look at Concurrent Mode and Suspense for data fetching.
Why Concurrent Mode?
JavaScript frameworks or libraries work with one thread. So, when a block of code runs, the rest of the blocks must wait for execution. They cannot do any work concurrently. This concept applies to rendering as well.
Once React starts rendering something, you cannot interrupt it. React developers refer to this rendering as “blocking rendering”. This blocking rendering creates a user interface that is jerky and stops responding from time to time. Let’s take a deeper look at the problem.
The Problem
Imagine we have an application that displays a long list of filterable products. We have a search box where we can filter records. We want to re-render the UI and the list every time the user presses a key.
If the list is long, the UI “stutters,” that is, the rendering is visible to the user. The stuttering degrades performance as well. Developers can use a few techniques like throttling and debouncing, which help a little but are not a solution.
Throttling limits the number of times a particular function gets called. Using throttling, we can avoid repeated calls to expensive and time-consuming APIs or functions. This process dramatically increases performance, especially for rendering information on the user interface.
Debouncing ignores calls to a function for a predetermined amount of time. The function call is made only after a predetermined amount of time has passed.
You can see the stutter in the image below.
While waiting for the non-urgent API calls for finishing, the UI stutters, which blocks rendering the user interface. The solution is interruptible rendering using Concurrent Mode.
Interruptible Rendering
With interruptible rendering, React.js does not block the UI while it processes and re-renders the list. It makes React.js more granular by pausing trivial work, updating the DOM, and ensuring that the UI does not stutter.
React updates or repaints the input box with the user input in parallel. It also updates the list in memory. After React finishes the updates, it updates the DOM and re-renders the list on the user’s display. Essentially, interruptible rendering enables React to “multitask.” This feature provides a more fluid UI experience.
Concurrent Mode
Concurrent Mode is a set of features that help React apps stay responsive and smoothly adjust to a user’s device and network speed capabilities.
Concurrent Mode divides the tasks it has into even smaller chunks. React’s scheduler can pick and choose which jobs to do. The scheduling of the jobs depends on their priority. By prioritizing tasks, it can halt the trivial or non-urgent or push them further. React always gives top priority to User interface updating and rendering.
Using Concurrent Mode, we can:
- Control the first rendering process
- Prioritize the rendering processes
- Suspend and resume rendering of components
- Cache and optimize runtime rendering of components
- Hide content from the display until required
Along with the UI rendering, Concurrent Mode improves the responses to incoming data, lazy-loaded components, and asynchronous processes. Concurrent Mode keeps the user Interface reactive and updated while it updates the data in the background. Concurrent Mode works with two React hooks: useTransition and useDeferredValue.
Using the Deferred Value Hook
The syntax of the useDeferredValue hook is,
const deferredValue = useDeferredValue(value, { timeoutMs: <some value> });
This command causes the value to “lag” for the time set in timeoutMs. The command keeps the user interface reactive and responsive, whether the user interface must be updated immediately or must wait for data. This hook avoids the UI stutter and keeps the user interface responsive at all times at the minor cost of keeping fetched data lagging.
Using the Transition Hook
The useTransition hook is a React hook used mainly with Suspense. Consider the following scenario where there is a webpage with buttons with user’s names on them. With the click of a button, the webpage display’s the user’s details on the screen.
Let’s say the user first clicked one button and then the next. The screen either goes blank, or we get a spinner on the screen. If fetching details takes too long, the user interface could freeze.
The useTransition method returns the values of two hooks: startTransition and isPending. The syntax for the useTransition method is:
const [startTransition, isPending] = useTransition({ timeoutMs: 3000 });
The startTransition syntax is:
<button disabled={isPending}
startTransition(() => {
<fetch Calls>
});
</button>
{isPending? " Loading...": null}
Using the useTransition hook, React.js continues to show the user interface without the user’s details until the user’s details are ready, but the UI is responsive. React prioritizes the user interface to keep it responsive while fetching data in parallel.
Suspense for Data Fetching
Suspense is another experimental feature that React introduced along with Concurrent Mode. Suspense enables components to wait for a predetermined period before they render.
The primary goal of Suspense is to read data from components asynchronously without bothering about the source of the data. Suspense works best with the concept of lazy loading.
Suspense allows data fetching libraries to inform React of whether data components are ready for use. With this communication, React will not update the UI until the necessary components are ready.
Suspense enables:
- Integration between data fetching libraries and React components
- Controlling the visual loading states
- Avoiding race conditions
The basic syntax of a Spinner component is as follows:
import Spinner from './Spinner';
<Suspense fallback={<Spinner />}>
<SomeComponent />
</Suspense>
Suspense used in the Concurrent Mode allows the time-consuming components to start rendering while waiting for data. It shows placeholders in the meantime. This combination produces a more fluid UI experience.
Suspense and Lazy Components
React.lazy is a new function that enables React.js to load components lazily. Lazy loading means that components are loaded (their code is retrieved and rendered) only when needed. They prioritize the most critical user interface components. React developers recommend wrapping lazy components inside the Suspense component.
Doing so ensures that when the component is rendering, there are no “bad states.” The user interface remains responsive throughout the process and leads to a more fluid user experience.
Enabling Concurrent Mode
To enable the Concurrent Mode, install the experimental react version. The prerequisite to installing React is the node packet manager (npm). To install the experimental version, issue a command at a command prompt.
npm install react@experimental react-dom@experimental
To test whether the experimental build is set up, create a sample React app. Without the experimental features the render code is as follows:
import * as React from 'react';
import { render } from 'react-dom';
render(<App />, document.getElementById('root'));
In the Concurrent Mode, write this code:
import * as React from 'react';
import { createRoot } from 'react-dom';
createRoot(document.getElementById('root')).render(<App />);
This enables the Concurrent mode for the entire application.
React splits the rendering call into two parts:
- Create the root element
- Use the rendering call
Currently, React plans to maintain three modes:
- Legacy Mode is the traditional or current mode for backward compatibility
- Blocking Mode is an intermediate stage in Concurrent Mode development
- Concurrent Mode
The Blocking Mode uses the createBlockingRoot call instead of the createRoot call. The Blocking Mode gives developers a chance to fix bugs and solve problems during Concurrent Mode development.
React documentation claims that each mode supports the following features:
Example Application
We have created a pixels app to demonstrate the difference between the Concurrent Mode and other modes that use traditional or block rendering. The pixels app is a 150 by 150 canvas with random pixels and a search box. Every time a user types in the search box, the canvas re-renders itself.
Even though the UI does not render in the Concurrent Mode, user input does not stall updates. The pixel canvas re-renders after the processing completes. In the Legacy Mode, when typing fast, the UI halts and sometimes stalls before it can render the canvas again. User input also stalls and does not update.
The main file for building the pixels app is canvas.js. We have also made an input box where the user can input anything. Each press of a key re-renders the canvas of pixels.
Code Files
Index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
// Traditional or non-Concurrent Mode react
const rootTraditional = document.getElementById("root");
ReactDOM.render(<App caption="Traditional or Block Rendering" />,
rootTraditional);
// Concurrent Mode enabled
const rootConcurrent = document.getElementById("root-concurrent");
ReactDOM.createRoot(rootConcurrent).render(<App caption="Interruptible
Rendering" />);
App.js
import React, { useState, useDeferredValue } from "react";
import "./App.css";
import { Canvas } from "./Canvas";
export default function App(props)
{ const [value, setValue] = useState("");
//This is available only in the Concurrent mode.
const deferredValue = useDeferredValue(value, {
timeoutMs: 5000
});
const keyPressHandler = e => {
setValue(e.target.value);
};
return (
<div className="App">
<h1>{props.caption}</h1>
<input onKeyUp={keyPressHandler} />
<Canvas value={deferredValue} />
</div>
);
}
Canvas.js
import React from "react";
const CANVAS_SIZE = 70;
const generateRandomColor = () => {
var letters = "0123456789ABCDEF";
var color = "#";
for (var i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
};
const createCanvas = (rows, columns) => {
let array = [];
for (let i = 0; i < rows; i++) {
let row = [];
for (let j = 0; j < columns; j++) {
row.push(0);
}
array.push(row);
}
return array;
};
//This is the square with the pixels
const drawCanvas = value => {
const canvas = createCanvas(CANVAS_SIZE, CANVAS_SIZE);
return canvas.map((row, rowIndex) => {
let cellsArrJSX = row.map((cell, cellIndex) => {
let key = rowIndex + "-" + cellIndex;
return (
<div
style=\{{ backgroundColor: generateRandomColor() }}
className="cell"
key={"cell-" + key}
/>
);
});
return (
<div key={"row-" + rowIndex} className="canvas-row">
{cellsArrJSX}
</div>
);
});
};
export const Canvas = ({ value }) => {
return (
<div>
<h2 style=\{{ minHeight: 30 }}>{value}</h2>
<div className="canvas">{drawCanvas(value)}</div>
</div>
);
};
Index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<meta name="theme-color" content="#000000" />
<title>React App Concurrent Mode</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="container">
<div id="root" class="column"></div>
<div id="root-concurrent" class="column"></div>
</div>
</body>
</html>
Running the Code
Let’s look at our code in action. The first screen we see is the initial screen. Using traditional or block rendering is how React does it today. Interruptible Rendering is an experimental feature of Concurrent rendering. We look at traditional rendering working first.
The pixel canvas re-renders itself on every keystroke. In traditional rendering, the entire UI halts for every keystroke until it can re-render the screen. During this time, the user input is also not accepted even though we continue typing.
The next screen shows interruptible rendering. In interruptible rendering, the user can keep typing. The UI does not stall or halt while the canvas is re-rendered for every keystroke in parallel.
Once the re-rendering completes, React updates the UI. Although difficult to see in a still screenshot, we can see that the grid is changing, but the user can still type without the UI stuttering.
Summary
In this article, we looked at React's experimental features, Concurrent Mode and Suspense. Using Concurrent Mode, React.js keeps the user interface responsive at all times.
It breaks the application’s tasks into smaller chunks and allows prioritization of user interface tasks. This mode, therefore, provides a more fluid and seamless user experience and increases an application’s overall performance.
Coupled with Concurrent Mode, Suspense allows the user interface to remain responsive. At the same time, heavy and time-consuming tasks like data fetching and so on can be done in parallel, thus providing an overall seamless experience.
The complete details about Concurrent Mode are available in React documentation.