Back

Concurrent Mode in React: an overview

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, allowing 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 when 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 render 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 tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

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.

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs and track user frustrations. Get complete visibility into your frontend with OpenReplay, the most advanced open-source session replay tool for developers.

OpenReplay