Concurrent Rendering in React ⛽🛣️
Tags: javascript, webdev, react, frontend
Published: 2024-04-28 03:37:47

In this post, we will discuss the concept of _concurrent_ rendering in **React 18**, which allows us to **improve** user experience in new and exciting ways.

So, buckle up, and let's dive into the world of _concurrency_ in React!

#### What is Concurrency ?
**Concurrency** means _executing multiple tasks at the same time but not necessarily **simultaneously**_. In a concurrent application, two tasks can start, run and complete in overlapping time periods, without waiting for one to finish before starting another. i.e Task-2 can start even before Task-1 gets completed.
![Concurrency](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mmldrkws0b3q2lf1g1x0.png)

### Concurrency in React, Explained Simply

In the context of React.js, concurrency refers to having more than one task in progress at once without blocking the main thread, and concurrent tasks can overlap depending on which is more urgent. This is a stark contrast to the traditional, synchronous rendering model where React would block the main thread until it finished rendering the component tree.

For example, while you're writing, you're also preparing vegetables to eat. When the time comes to add ingredients to the vegetables, that will be more urgent, so you'll pause writing and attend to that and come back to continue writing when you're done. 
At different points throughout the writing and cooking processes, your focus will be on what is more urgent.

React could only handle one task at a time in the past, and a task could not be interrupted once it had started. This approach is referred to as **Blocking Rendering**. To fix this problem, **Concurrent Mode** was introduced—which makes rendering interruptible.

#### What is Concurrent Rendering in React ?
In the realm of React, concurrent rendering is a game-changer. It's a new way of rendering that helps your app stay responsive under heavy load and gives you more control over how updates get scheduled.
Concurrent rendering is all about creating a fluid user experience. It allows React to interrupt an ongoing rendering process to handle more urgent tasks. This way, even if your app is in middle of a large rendering task, it can still respond immediately to user interactions.

#### Say Bye to Concurrent Mode
Concurrent Mode was introduced as an experimental feature. In favor of a more logical adoption that allows you to opt in to concurrent rendering at your conveniences. _**Concurrent Mode**_ is now being replaced in React 18 with _**concurrent features**_.

Here's an example to illustrate the difference:

```javascript
// React 16
ReactDOM.render(<App />, document.getElementById('root'));

// React 18
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
```

In React 16, we use **ReactDOM.render** to render our app. 
In React 18, we use **ReactDOM.createRoot** to create a root node and then call render on that root node. This enables concurrent rendering for the entire app.

## Concurrent Features in React 18
Let’s look at a few more concurrency features that React 18 introduces.

### Automatic batching
React 18 introduces great key feature is automatic batching. which enhances rendering effectiveness, performance and responsiveness. 

**Batching** is when React groups multiple state updates into a single re-render for better performance.

In React 17 and prior, were **_batched_** update occur only the state updates defined inside React event handlers. But updates inside of promises, setTimeout, native events handlers, or any other event were not _**batched**_ in React by default.

Here's the example, React 17 and prior versions:

```javascript
const [increase, setIncrease] = useState(0);
const [decrease, setDecrease] = useState(0);

const handleOnClick = () => {
     setIncrease(increase + 1)
     setDecrease(decrease - 1)
   };

console.log('Rendering');

return (
    <div className="wrapper">
      <h1>Counter details</h1>
      <p>
        Increase: <strong>{increase}</strong>
      </p>
      <p>
        Decrease: <strong>{decrease}</strong>
      </p>
      <button onClick={handleOnClick}>Click Me</button>
    </div>
  );
```

In the above example, the `handleOnClick()` function will be called when a user clicks on the button. It will execute two-state updates on each click.

If you observe the browser console, you will see that the **“Rendering”** message is logged only **once** for _**both state updates**_.

Now you have seen that React **batched** updates both states and re-rendered the component only **once** inside **event handler**.

But, what if we execute state updates in a context that is not associated with the browser?

For example, consider a `setTimeout()` call that asynchronously loads data:
```javascript
  const [increase, setIncrease] = useState(0);
  const [decrease, setDecrease] = useState(0);

  const handleOnClick = () => {
  // updates state inside of setTimeout
    setTimeout(() => {
      setIncrease(increase + 1)
      setDecrease(decrease - 1)
    }, 0)
  }

  console.log('Rendering');

  return (
    <div className="wrapper">
      <h1>Counter details</h1>
      <p>
        Increase: <strong>{increase}</strong>
      </p>
      <p>
        Decrease: <strong>{decrease}</strong>
      </p>
      <button onClick={handleOnClick}>Click Me</button>
    </div>
  );
```
In the above example, the state update occurs in the callback of `setTimeout()` function.

If you observe the browser console after executing this example, you will see 2 messages. This indicates that **two separate re-renders** occur for **each state update**.

This behaviour can cause severe performance issues related to application speed. That’s why React introduced **Automatic Batching**.

In **React 18** we are introduced to improved version of batching called **Automatic Batching**. It will enabled batching of all the state updates regardless of where they are called. This includes updates inside promises, timeouts, and native event handlers.

Let’s consider the previous example to understand how **automatic batching** works:
```javascript
  const [increase, setIncrease] = useState(0);
  const [decrease, setDecrease] = useState(0);

  const handleOnClick = () => {
  // updates state inside of setTimeout
    setTimeout(() => {
      setIncrease(increase + 1)
      setDecrease(decrease - 1)
    }, 0)
  }

  console.log('Rendering');

  return (
    <div className="wrapper">
      <h1>Counter details</h1>
      <p>
        Increase: <strong>{increase}</strong>
      </p>
      <p>
        Decrease: <strong>{decrease}</strong>
      </p>
      <button onClick={handleOnClick}>Click Me</button>
    </div>
  );
```
The above example shows an optimal re-rendering process where `handleOnClick()` function will be called when a user clicks on the button.It will execute two-state updates on each click and give only _**one re-render**_ as React batches all state updates regardless of the invocation location.

However, Automatic batching significantly reduces rendering iterations, which enhances performance and responsiveness.


### SSR with Suspense
Server-side rendering is a technique that allows us to generate HTML from React components on the server and then send a fully rendered HTML page to client. After the HTML is rendered in the browser, the React and JavaScript code for the entire app starts loading. When it is done, It adds interactivity to static HTML generated on the server—a process known as **hydration**.

#### Why Suspense ?
In the previous versions of React, hydration could only begin after the entire data had been fetched from the server and rendered to HTML. Additionally, your users couldn’t interact with the page until hydration was complete for the whole page.

To solve this problem, the **<Suspense />** component was released in 2018. The only supported use case was **lazy-loading** code on the client, not during server rendering. 

Let’s start with how the `<Suspense>` component works with the lazy loading components.

When using the Suspense component, you must provide a fallback option. It accepts any React components or even a string to render while the lazy component is loading.

Take a look at the example below, which uses the Suspense wrapper to display a typical loader until the `<LazyLoadComponent>` is ready.

```js
import { lazy, Suspense } from 'react';
const LazyLoadComponent = lazy(
      () => import('./LazyLoadComponent')
);

function MyComponent() {
  return (
    <Suspense fallback={<Loader />}>
      <div>
        <LazyLoadComponent />
      </div>
    </Suspense>
  );
}
```
Now, let’s take a look at the enhancements Suspense received in React 18.

Suspense is no longer limited to lazy components, React 18 added support for Suspense on the server. React 18 offers two major features for SSR unlocked by Suspense:

#### 1. Streaming Server Rendering

React 18 introduces new server rendering APIs that allow you to stream HTML from the server to the client. This means **Streaming HTML** is just sending lightweight static HTML. It’s replaced by heavier components that depend, without waiting for the entire response.

```javascript
<Layout>
  <Article />
  <Suspense fallback={<Spinner />}>
    <Comments />
  </Suspense>
</Layout>

```

#### 2. 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. 

### Transitions
A _transition_ is a new concept in React to distinguish between _urgent_ and _non-urgent_ updates.

- **Urgent updates** reflect direct interaction, like typing, clicking, pressing, and so on.
- **Transition updates** transition the UI from one view to another.

Transitions API gives us an **imperative** way of marking some update as low priority.

#### StartTransition API
The **startTransition** API can be used to mark any state updates as non-urgent. The `startTransition` API receives a function as argument such that any state updates that occur within this function are marked as a low priority update.

> startTransition: a method to start transitions when the hook cannot be used.

In the code block below, there are two state updates- one that is urgent and other is marked as non-urgent as it's wrapped inside the `startTransition` callback:

```javascript
import {startTransition} from 'react';

// Urgent: Show what was typed
setInputValue(input);

// Mark any state updates inside as transitions (non-urgent)
startTransition(() => {
  // Transition: Show the results
  setResults(input);
});
```
Non-urgent updates will be **interrupted** if more urgent updates like `clicks` or `key presses` come in. If a transition gets interrupted by the user (for example, by typing multiple characters in a row) or start transition is called again before the previous call is completed, React by default cancels older transitions and render only the latest update.

#### useTransition Hook

React can also track and update pending state transitions using the `useTransition` hook.

> useTransition: a hook to start transitions, including a value to track the pending state.

Invoking `const[isPending, startTransition] = useTransition()` returns an array of **two** items:

- `isPending`: indicates that the transition is pending.
- `startTransition(callback)`: allows you to mark any UI updates inside `callback` as transitions or which dispatches a new concurrent render.

As already mentioned, you can use `useTransition()` hook to let know React which UI updates are urgent (like updating the input field value), and which are non-urgent transitions (like updating the names list to highlight the query).

First, let's invoke the `[isPending, startTransition] = useTransition()` hook to get access to `startTransition()` function. Secondly, let's create a state variable to hold the query state value specifically for the transition.

```javascript
import { useState, useCallback, useTransition } from 'react';

function MyCounter() {
    const [isPending, startTransition] = useTransition();
    const [count, setCount] = useState(0);
    const increment = useCallback(() => {
        startTransition(() => {
            // Run this update concurrently
            setCount(count => count + 1);
        });
    }, []);
 
    return (
        <>
            <button onClick={increment}>Count {count}</button>
            <span>{isPending ? "Pending" : "Not Pending"}</span>
            // Component which benefits from concurrency
            <ManySlowComponents count={count} />
        </>
    )
}
```
An important caveat of `useTransition` is that it cannot be used for controlled inputs. 

```javascript
const [text, setText] = useState('');
// ...
function handleChange(e) {
  // ❌ Can't use Transitions for controlled input state
  startTransition(() => {
    setText(e.target.value);
  });
}
// ...
return <input value={text} onChange={handleChange} />;
```
This is because Transitions are non-blocking, but updating an input in response to the change event should happen synchronously. If you want to run a Transition in response to typing, you have two options:

- You can declare two separate state variables: one for the input state (which always updates synchronously), and one that you will update in a Transition. This lets you control the input using the synchronous state, and pass the Transition state variable (which will “lag behind” the input) to the rest of your rendering logic.
- Alternatively, you can have one state variable, and add useDeferredValue which will “lag behind” the real value. It will trigger non-blocking re-renders to “catch up” with the new value automatically.

For those cases, it is best to use `useDeferredValue.`

#### useDeferredValue Hook
The `useDeferredValue` hook is a React hook that allows you to defer updating a part of the UI. It is a convenient hook for cases where you do not have the opportunity to wrap the state update in `startTransition` but still wish to run the update concurrently.

An example of where this occurs is child components receiving a new value from the parent.

The `useDeferredValue` hook is a useful addition to React 18, aimed to improve component rendering performance by preventing unnecessary re-renders.

The `useDeferredValue` hook takes two arguments:

1. **value**: The value you want to defer. It can have any type.
2. **options**: An object that can have the 2 properties **timeoutMs** and **scheduler**

- **timeoutMs**: The time in milliseconds to wait before updating the value.
- **scheduler**: A function that will be used to schedule the update.

The `useDeferredValue` hook returns a value that is the deferred version of the value you provided. The deferred value will be updated after the specified amount of time, or immediately if the value changes.

Conceptually, `useDeferredValue` similar to debouncing, but has a few advantages compared to it. There is no fixed time delay, so React will attempt the deferred render right after the first render is reflected on the screen. The deferred render is interruptible and doesn’t block user input. 

Here is an example of how to use the `useDeferredValue` hook:
```javascript
import React, { useState, useDeferredValue } from 'react';
  
const [count, setCount] = useState(0);
  const deferredCount = useDeferredValue(count, {
    timeoutMs: 1000,
  });
  const handleClick = () => {
    setCount(count + 1);
  };
  return (
    <div>
      <h1>Counter</h1>
      <p>Current count: {count}</p>
      <button onClick={handleClick}>Click me</button>
      <p>Deferred count: {deferredCount}</p>
    </div>
  );
```
In this example, the `count` state variable is updated immediately when the user clicks the button. However, the `deferredCount` value is not updated immediately. Instead, it is updated after 1000 milliseconds. This means that the `deferredCount` value will not reflect the latest value of the `count` state variable until after 1000 milliseconds have passed.
## The Future of React with Concurrent Mode
Concurrent mode is not just a new feature, it's a whole new way of thinking about React. It allows React to work on multiple tasks at once, without blocking the main thread. It introduces powerful new features like automatic batching, streaming server rendering, and React Suspense. And it changes how we think about rendering and state updates in React.

So, as we look to the future of React with concurrent mode. Use these new powers to create more responsive, user-friendly apps.