Concurrent Mode in React: an overview
Performance is a crucial issue in web development, especially among React developers. This article will explain how the problem is solved with concurrent mode, which enables React to render several components simultaneously, enhancing the application’s overall performance.
The need for speedier, agile apps has brought up the need to optimize web applications due to the rising demands for fast-responding user interfaces from clients. React has implemented concurrent mode, which enables it to render several components simultaneously. Understanding concurrent mode is critical, as it will undoubtedly influence how developers build and structure their components.
Here’s a quick overview of the areas to be covered in this article:
- What is the Concurrent Mode in React?
- How to use the new lifecycle methods
- Benefits of using Concurrent Mode
- Issues when using concurrent Mode in React
- Conclusion
Now, let’s try to understand what Concurrent mode in React is all in detail.
What is the Concurrent Mode in React?
We will start by looking at the word “Concurrent”. The Merriam-Webster dictionary defines it as “operating or occurring at the same time.”
In React, the Concurrent mode is a new feature in version 18. What does it do? With this feature, a developer is given the superpower to build components that render concurrently; that is, they render simultaneously without blocking the user interface. Therefore, a user can interact with your page without noticing a re-render.
This is aimed at increasing the performance of your application, and with that, you can prioritize what component renders, and you can update the part of the component tree that changed.
The algorithm used in achieving this magic is called the Time Slicing
algorithm. It allows React to break down the rendering into smaller and prioritized chunks called fiber nodes, which are then scheduled based on priority and expiration time. The most critical part of the user interfaces is the highest priority fiber nodes, such as your buttons and menus, which need to be updated quickly, unlike the lowest priority fiber nodes, such as a different tab or a part of the page are not visible yet. This is done to improve the user experience and interface performance so that the user can continue interacting with the page while rendering is in progress.
In React Concurrent
mode feature, new APIs are added for rendering scheduling and managing asynchronous updates, such as the unstable scheduleCallback and unstable cancelCallback methods. These APIs enable developers to control how rendering is prioritized and postpone updates until they are ready, improving performance even further.
Here’s an example of the concurrent mode in React:
import React, { unstable_ConcurrentMode as ConcurrentMode } from 'react';
function App() {
return (
<ConcurrentMode>
<div>
<h1>Hello, world!</h1>
</div>
</ConcurrentMode>
);
}
export default App;
In the example above, we imported the unstable_ConcurrentMode
as ConcurrentMode
from the react package. We went ahead to wrap our component with the ConcurrentMode
component. Doing this enables the Time Slicing algorithm to prioritize rendering based on importance.
It is also important to note that unstable_ConcurrentMode
is marked as unstable because it is still an experimental feature when writing this article and may likely change in future releases.
Let’s see how we can implement these new API methods introduced in the next section.
How to use the new lifecycle methods
As we said earlier, Concurrent
Mode in React adds a new set of lifecycle methods for optimizing the rendering performance of your components. Here's a rundown of these new lifecycle methods:
- `useTransition`: If you want to create a transition between two different components’ rendering, this hook provides a way to handle that as it allows you to defer the next component’s rendering until the previous one is complete. Here’s an example:
import { useTransition } from 'react';
function MyComponent() {
const [isPending, startTransition] = useTransition({ timeoutMs: 1000 });
function handleClick() {
startTransition(() => {
// write logic to perform some state updates or async operations here
});
}
return (
<button onClick={handleClick}>
{isPending ? 'Loading...' : 'Click me!'}
</button>
);
}
In this example, the useTransition
hook creates a smooth transition between the rendering of two different button states. The startTransition function starts the transition and updates the button's state. While the transition is in progress, the isPending
value is used to display a loading indicator.
- `useDeferredValue`: You can use this hook to defer the rendering of a value until the main thread is free to handle it. It can be useful in situations where you rely on an expensive calculation before rendering a component. Here’s an example:
import { useDeferredValue } from 'react';
function MyComponent({ expensiveValue }) {
const deferredValue = useDeferredValue(expensiveValue, { timeoutMs: 500 });
return (
<div>
<p>The deferred value is: {deferredValue}</p>
</div>
);
}
In the example above, the useDeferredValue
hook is used to defer the rendering of an expensive value until the main thread is free to handle it. As the hook's first argument, the expensiveValue prop is passed. The second argument is an options object with the timeout for rendering the deferred value specified.
The expensive value is calculated synchronously and passed to the useDeferredValue
hook when MyComponent
is first rendered. The hook returns a deferred value that is immediately rendered. However, the actual rendering of the deferred value is postponed until the main thread is free to render it, as specified by the timeout.
This can aid in optimizing MyComponent
's rendering performance, especially if the expensive value takes a long time to calculate or depends on external APIs or data sources. MyComponent
can render quickly and without blocking the main thread by deferring the rendering of the expensive value.
- `useOpaqueIdentifier`: To generate a unique identifier for a component, you can use this hook to achieve that. For example, in a situation where you are rendering sibling components that share the same identifier. Here’s an example:
import { useOpaqueIdentifier } from 'react';
function MyComponent() {
const id = useOpaqueIdentifier();
return (
<div>
<p>This component has ID: {id}</p>
</div>
);
}
The useOpaqueIdentifier
hook is used in this example to generate a unique identifier for the component. The id value is a string that can be used to improve the rendering of siblings with the same identifier.
The useOpaqueIdentifier
hook generates a unique identifier for MyComponent
when it is first rendered. This identifier is opaque, which means it has no inherent meaning or structure other than its uniqueness. The id value can be used in MyComponent
's rendering logic to optimize the rendering of sibling components with the same identifier. If MyComponent
has a sibling component that also uses the useOpaqueIdentifier
hook and has the same id value as MyComponent
, React can avoid unnecessary re-renders of the sibling component when MyComponent
is updated, helping to improve performance, especially if there are many sibling components with the same identifier as MyComponent
.
- `useMutableSource`: This hook reads data from a mutable data source, such as a cache or store. It enables you to subscribe to data source changes and re-render your component only when necessary. Here’s an example:
import { useMutableSource, useCallback, useState } from 'react';
const myDataSource = {
get: () => {
// Return some data
},
subscribe: (callback) => {
// Subscribe to updates
return () => {
// Unsubscribe from updates
};
},
};
function MyComponent() {
const [data, setData] = useState(null);
const getData = useCallback((source) => {
const newValue = source.get();
setData(newValue);
}, []);
useMutableSource(myDataSource, getData);
return (
<div>
<p>The data is: {data}</p>
</div>
);
}
The useMutableSource
hook is used in this example to subscribe to updates from a mutable data source (myDataSource). The getData
function is a callback that is called whenever the data source changes.
When MyComponent
is first rendered, the useMutableSource
hook subscribes to mySource updates and calls the getData function with the data source's current value. The setData function updates the component's state with the new data value.
The subscribe method is called when the data source is updated, with the getData function as a callback. When the callback is called, the new value of the data source is passed to the getData function, which updates the component’s state.
This can help optimize MyComponent
's rendering performance, especially if the data source is mutable and frequently updated. MyComponent
can render quickly and without blocking the main thread by subscribing to data source updates.
Having seen how to use the new lifecycles that come along with concurrent mode, it is also great to look at what we stand to gain by using it in the future.
Session Replay for Developers
Uncover frustrations, understand bugs, and fix slowdowns like never before with OpenReplay — an open-source session replay suite for developers. It can be self-hosted in minutes, giving you complete control over your customer data
Benefits of using Concurrent Mode
Even though we touch on some benefits of using Concurrent
mode in React earlier, we want to reiterate and list more as there are several advantages:
It improves the application’s performance: This is the primary benefit as it breaks the component into smaller, prioritized chunks, rendered efficiently according to importance, resulting in faster user interface response and rendering.
It boosts user experience: The rendering of component trees based on importance, especially when dealing with large and complex component trees, results in better user experiences.
Predictable rendering: Concurrent
Mode can also improve rendering predictability, especially when dealing with real-time data updates.
Looking at the issues or bad sides of using concurrent mode.
Issues when using Concurrent Mode in React
As the saying goes, “Anything that has advantages also has disadvantages”. Concurrent mode is not left out, especially since it is still an experimental feature in React v18. Here are some of the issues to be considered:
- API Instability: With each future release, the API may change, making it a big challenge to use in production environments as this may call for code refactoring and upgrade to keep up with the latest trend.
- Complexity increase: It can add complexity to your codebase, particularly if the React fiber architecture was not used before, and understanding the inner workings of the Time Slicing algorithm may look complex.
- Limited Browser Support: While React’s
Concurrent
Mode is intended to work in all modern browsers, some older browsers may not support it fully or at all. This limited browser support may impact the user experience for some users, particularly those using older or less popular browsers.
Conclusion
Concurrent
mode is a new feature introduced in React v18 to help tackle the performance issue. It uses an algorithm called the Time Slicing technique that breaks the user interface into smaller, prioritized chunks, which get rendered by order of importance, thereby increasing the response time for user interactions with the interfaces. It helps especially for large and complex components tree where only the node with changes is re-rendered.
It comes with several APIs such as useMutableSource, useDeferredValue
, and useTransition
, among others. These APIs may change with each future release and can make your codebase complicated since you have to keep us with the latest trend, and so it might not be good for the production environment.
Despite the setbacks, overall, it is a great go-to for performance sakes as it can be used to predict component tree rendering also.
Originally published at https://blog.openreplay.com.