Telerik blogs
ReactT2_1200x303

In this post, we will discuss the concept of concurrent rendering in React 18 and the new features that depend on this new mechanism.

React 18 was released in beta in November with new features and out-of-the-box improvements to already-existing features. This React release supports what is called Concurrent Features, which allow you to improve your user experience in new and exciting ways.

Concurrency in React, Explained Simply

In the context of React.js, concurrency refers to having more than one task in progress at once, and concurrent tasks can overlap depending on which is more urgent. For example, while I write this post, I am preparing Jollof rice (a Nigerian delicacy). When the time comes to add ingredients to the meal, that will be more urgent, so I’ll pause writing and attend to that and come back to continue writing when I’m done. Meanwhile, my food will still be cooking. At different points throughout the writing and cooking processes, my 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.

React gets easier when you have an expert by your side. KendoReact is a professional UI component library on a mission to help you design & build business apps with React much faster. Check it out!

Say Bye to Concurrent Mode

Concurrent Mode was introduced as an experimental feature. In favor of a more gradual adoption plan that allows you to opt in to concurrent rendering at your own pace, Concurrent Mode is now being replaced in React 18 with concurrent features.

Concurrent Rendering and Concurrent Features in React 18

Concurrent rendering describes how the new features (concurrent features) included in React 18 are implemented. With concurrent rendering, you can improve your app’s performance by declaring some state updates as non-urgent to keep the browser responsive. It will be automatically enabled in the specific parts of your app that use the new concurrent features because concurrent features were built on top of concurrent rendering.

StartTransition API

The startTransition API introduced with React 18 helps you keep your app responsive without blocking your user interactions by allowing you to mark specific updates as transitions.

There are two categories of state updates in React:

  • Urgent updates: show direct interaction like clicking, typing, etc.
  • Transition updates: change UI views

React considers state updates wrapped in startTransition as non-urgent, so they can be suspended or interrupted by urgent updates.

For example, as a user, it would feel more natural to see the letters as you type in a search input field for filtering data, but as expected, the search result may take a while, and that’s OK.

    import { startTransition } from 'react';
    
    // Urgent
    setInputValue(input);
    
    // Mark any state updates inside as transitions
    startTransition(() => {
      // Transition
      setSearchQuery(input);
    })

In React, all updates are handled as urgent by default, but in React 18, you can mark an update as a transition by wrapping it in a StartTransition API, as seen above. (You can learn more about the StartTransition hook in this article.)

useTransition API

React can also track and update pending state transitions using the useTransition hook with an isPending flag. This lets you display loading feedback to your users, letting them know that work is happening in the background.

    import { useTransition } from 'react';
    
    const [isPending, startTransition] = useTransition();
    
    {isPending && <Spinner />}

useDefferedValue API

This API keeps the UI responsive by telling React to defer updating the parts of a screen that take too long. For example, if we have a part of a UI that renders immediately and another part that needs to take some time, we can defer the part that requires more time by showing its old value while other components update.

useDefferedValue takes in a state value and a timeout in milliseconds and returns the deferred version of that state value. The timeout tells React how long it should delay the deferred value.

    import { useState, useDeferredValue } from "react";
    
    function App() {
      const [input, setInput] = useState("");
      const deferredValue = useDeferredValue(text, { timeoutMs: 3000 }); 
    
      return (
        <div>
          <input value={input} onChange={handleChange} />
          <MyList text={deferredValue} />
        </div>
      );
     }

With what we have in the code snippet above, the input value will be displayed immediately when a user starts typing, but useDeferredValue will display a previous version of the MyList component for at most 3000 milliseconds.

Server-Side Rendering 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 the client. Your users won’t be able to interact with the HTML, but it will provide them with content to see before your JavaScript bundle loads. After the HTML is rendered in the browser, the React and JavaScript code for the entire app starts loading. When it is done, it connects the JavaScript logic to the server-generated HTML—a process known as hydration.

When the hydration process for the whole page is complete, your users can fully interact with your application. 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. The problem with this approach is that for every step, the parts of your application that load fast will always have to wait for the parts that are slow to finish loading, and this isn’t very efficient.

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. But React 18 added support for Suspense on the server. React 18 offers two major features for SSR unlocked by Suspense:

  1. Streaming HTML on the server

You can wrap a part of the page with Suspense and provide a fallback prop. Suspense is a component that allows us to specify what should happen when code for another component isn’t ready or is taking some time. This instructs React not to wait for that component to load but instead continue streaming HTML for the rest of the page. React will display the component you provided as a fallback to your users while waiting for the component to load.

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

In the code snippet above, we wrapped the Comments component in a Suspense boundary and provided a fallback prop (Spinner) to be displayed. React will go ahead and render the Article, and when the HTML for the Comments becomes available on the server, it will be added to the same stream along with a script tag and inserted in the right place.

  1. Selective hydration

As seen above, wrapping our Comments component in Suspense tells React not to block the streaming of the HTML for our Article component from the server. It also tells React not to block hydration by waiting for all the JavaScript code to load. This means that React can start hydrating the rest of the page, and when the HTML for the Comments section is ready, it’ll get hydrated.

<Layout>
  <Article />
  <Suspense fallback={<Loader />}>
    <Comments />
    <Card />
  </Suspense>
</Layout>

Another cool improvement added to Suspense is that if a user interacts with a component wrapped in Suspense whose HTML has been loaded but is yet to be hydrated, React will prioritize hydrating the parts that the user interacted with before hydrating the rest of the app. See here for more on the Suspense SSR architecture for React 18.

How To Opt Into Concurrency

First, you need to upgrade your React and ReactDOM npm packages to React 18, and then you also need to move from ReactDOM.render to ReactDOM.createRoot. After this, you automatically opt into concurrent rendering in the parts of your app that use the concurrent features.

import * as ReactDOM from 'react-dom';
import App from 'App';
const container = document.getElementById('app');
// Create a root
const root = ReactDOM.createRoot(container);
// Initial render
root.render(<App />);

This new root API adds out-of-the-box improvements and gives us access to the concurrent features.

Conclusion

In this post, we discussed the concept of concurrent rendering in React 18 and the new features—concurrent features—that depend on this new mechanism, which we have access to by default when we opt into concurrency.

Resources You May Find Useful


Ifeoma-Imoh
About the Author

Ifeoma Imoh

Ifeoma Imoh is a software developer and technical writer who is in love with all things JavaScript. Find her on Twitter or YouTube.

Related Posts

Comments

Comments are disabled in preview mode.