DEV Community

Cover image for Getting started with startTransition in React 18
Matt Angelosanto for LogRocket

Posted on • Originally published at blog.logrocket.com

Getting started with startTransition in React 18

Written by Arjuna Sky Kok ✏️

Part of React 18’s experimental Concurrent Mode is a new feature called startTransition, which prevents an expensive UI render from being executed immediately.

To understand why we need this feature, remember that forcing expensive UI renders to be done immediately can block lighter and more urgent UI renders from rendering in time. This can frustrate users who need immediate response from the urgent UI renders.

An example of an urgent UI render would be typing in a search bar. When you type, you want to see your typing manifested and begin searching immediately. If the app freezes and the searching stops, you get frustrated. Other expensive UI renders can bog down the whole app, including your light UI renders that are supposed to be fast (like seeing search results as you type).

When developing your React app, you can avoid this problem by debouncing or throttling. Unfortunately, using debouncing or throttling can still cause an app to become unresponsive.

startTransition allows you to mark certain updates in the app as non-urgent, so they are paused while the more urgent updates are prioritized. This makes your app feel faster, and can reduce the burden of rendering items in your app that are not strictly necessary. Therefore, no matter what you are rendering, your app is still responding to your user’s input.

In this article, we’ll learn how to use startTransition in your React app in order to delay the non-urgent UI updates to avoid blocking urgent UI updates. With this feature, you can convert your slow React app into a responsive one in no time.

Before we begin, note that React 18 is still in alpha at the time of writing, so startTransitionis not yet part of a stable release.

Getting started with React 18

Before beginning the tutorial, ensure you have the following:

  • Working knowledge of React
  • Node.js installed on your machine

Let’s begin by creating a React project with create-react-app:

$ npx create-react-app starttransition_demo
Enter fullscreen mode Exit fullscreen mode

The command above created a React project using the latest stable version of React, which is version 17. We need to use React 18. Go inside the project directory and remove the node_modules directory:

$ cd starttransition_demo/

$ rm -rf node_modules
Enter fullscreen mode Exit fullscreen mode

On Windows, you have to use a different command to remove the directory. After removing the directory, edit package.json. Find these lines:

    "react": "^17.0.2",

    "react-dom": "^17.0.2",
Enter fullscreen mode Exit fullscreen mode

Then, change the version of React from 17 to alpha:

    "react": "alpha",

    "react-dom": "alpha",
Enter fullscreen mode Exit fullscreen mode

Finally, install the libraries with yarn:

$ yarn install
Enter fullscreen mode Exit fullscreen mode

To make sure that you have React 18 installed, you can check it from the node_modules directory like so:

$ grep version node_modules/react/package.json

  "version": "18.0.0-alpha-6ecad79cc-20211006",
Enter fullscreen mode Exit fullscreen mode

On Windows, you can open the file directly.

Run the server to make sure you can run the React 18 app:

yarn start
Enter fullscreen mode Exit fullscreen mode

Open http://localhost:3000 in your browser. You should see the familiar default page of a React project with a rotating React logo.

Enabling Concurrent Mode

By default, our React project doesn’t support Concurrent Mode. We need to enable it by rendering the root React node in a different way.

Open src/index.js. You can see that we render the root node with the render static method from ReactDOM:

ReactDOM.render(

  <React.StrictMode>

    <App />

  </React.StrictMode>,

  document.getElementById('root')

);
Enter fullscreen mode Exit fullscreen mode

To enable Concurrent Mode, we need to create the root node first then use the render method from that instance. Change the lines above to the lines below:

const container = document.getElementById('root')

const root = ReactDOM.createRoot(container);

root.render(

  <React.StrictMode>

    <App />

  </React.StrictMode>

);
Enter fullscreen mode Exit fullscreen mode

Notice the createRoot method from ReactDOM. This will create a root node.

Setting up a testing environment

First, let’s create a React app with a light UI render and an expensive UI render. Open src/App.js. You can see the App function definition displaying a React logo, a p tag, and a link.

Replace the App function with the code below:

function App() {

  const [search_text, setSearchText] = useState("");

  const [search_result, setSearchResult] = useState();

  const handleChange = e => {

    setSearchText(e.target.value);

  };

  useEffect(() => {

    if (search_text==="") {

        setSearchResult(null);

    } else {

        const rows = Array.from(Array(5000), (_, index) => {

              return (

                      <div key={index}>

                    <img src={logo} className="App-logo" alt="logo" />

                    <div>{index + 1}. {search_text}</div>

                      </div>

                );

        });

        const list = <div>{rows}</div>;

        setSearchResult(list);

    }

  }, [search_text]);

  return (

    <div className="App">

        <header className="App-header">

            <div className="SearchEngine">

                <div className="SearchInput">

                    <input type="text" value={search_text} onChange={handleChange} />

                </div>

                <div className="SearchResult">

                    {search_result}

                </div>

            </div>

        </header>

    </div>

  );

}
Enter fullscreen mode Exit fullscreen mode

You need to import useEffect and useState. Put this line on top of the file:

import {useState, useEffect } from 'react';
Enter fullscreen mode Exit fullscreen mode

Here, we are creating the app’s UI that consists of two parts: the search input and the search result.

Because the input has a callback, when you type the text on the input, the text is passed as an argument to setSearchText to update the value of search_text using the useState hook. Then, the search result shows up. For this demo, the result is 5,000 rows where each row consists of a rotating React logo and the same search query text.

Our light and immediate UI render is the search input with its text. When you type text on the search input, the text should appear immediately. However, displaying 5,000 React logos and the search text is an expensive UI render.

Let’s look at an example; try typing “I love React very much” quickly in our new React app. When you type “I”, the app renders the text “I” immediately on the search input. Then it renders the 5,000 rows. This takes a long time, which reveals our rendering problem. The React app cannot render the full text immediately. The expensive UI render makes the light UI render become slow as well.

You can try it yourself on the app at http://localhost:3000. You’ll be presented with a search input. I have set up a demo app as well.

Screenshot of sample React app with searchbar that reads "I love React very much"

What we want is for the expensive UI render not to drag the light UI render to the mud while it loads. They should be separated, which is where startTransition comes in.

Using startTransition

Let’s see what happens when we import startTransition. Your top line import should be like this:

import {useState, useEffect, startTransition} from 'react';
Enter fullscreen mode Exit fullscreen mode

Then, wrap the expensive UI render in this function. Change setSearchResult(list) into the code below:

      startTransition(() => {

          setSearchResult(list);

      });
Enter fullscreen mode Exit fullscreen mode

Now, you can test the app again. When you type something in the search input, the text is rendered immediately. After you stop (or a couple of seconds pass), the React app renders the search result.

What if you want to display something on the search results while waiting for the expensive UI render to finish? You may want to display a progress bar to give immediate feedback to users so they know the app is working on their request.

For this, we can use the isPending variable that comes from the useTransition hook.

First, change the import line on the top of the file into the code below:

import {useState, useEffect, useTransition} from 'react';
Enter fullscreen mode Exit fullscreen mode

Extract isPending and startTransition from the useTransition hook. Put the code below on the first line inside the App function:

  const [isPending, startTransition] = useTransition();
Enter fullscreen mode Exit fullscreen mode

Next, change the content of <div className="SearchResult"> to the code below:

            {isPending && <div><br /><span>Loading...</span></div>}

            {!isPending && search_result}
Enter fullscreen mode Exit fullscreen mode

Now when you type the text on the search input very fast, the loading indicator is displayed first.

Screenshot of search bar in React app with Loading indicator

Conclusion

With startTransition, you can make the React app smooth and reactive by separating the immediate UI renders and the non-urgent UI renders. By putting all non-urgent UI renders inside the startTransition method, your app will be much more satisfying to use.

We also covered using the isPending variable to indicate the status of the transition in case you want to give feedback to users.

You can get the full code of the startTransition demo app here. You can also experiment with the demo of the app to your heart's content. Hopefully this knowledge will be useful for you when you build your next React app. Make sure the apps will be smooth!


Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket Dashboard Free Trial Banner

LogRocket is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — start monitoring for free.

Top comments (0)