AstroNode

Explain React 18 useTransition Hook with Examples

Explain React 18 useTransition Hook with Examples

React 18 introduced the useTransition hook. As a ReactJS developer, you must learn about it to improve your app's usability and responsiveness.

Tapas Adhikary
Tapas Adhikary.
May 2, 2023.7 min read

React 18 has changed how developers design and write ReactJS code. Including the Concurrency model and many other new hooks help tackle niche use cases like responsiveness, performance, etc.

In this article, we will discuss the hook I like most, the useTransition hook. It improves the application responsiveness by optimizing the performance. Let's take a deeper look at it.

If you like to learn from the video content, this article is also available as a YouTube Video with visualizations 😊

Don't forget to SUBSCRIBE for future content.

First, Let's Talk About the Problem

Yeah, right! Before we get introduced to the useTransition hook, let us understand the problem it solves so that we can appreciate it even more. Consider an application where you render a huge list of users and a textbox to search any of the users by their names.

If you have started imagining how the app may look, here is one of the simplistic versions that will work for this article.

The app

As we start typing a user name in the textbox to find the user, the user list below gets filtered like this:

Filtered the users

Great! Let us now consider the component we want to write to achieve the above functionality. Let's imagine the following code for the component.

import React, { useState, useTransition } from 'react';

export default function App({ users }) {
  const [searchTerm, setSearchTerm] = useState('');
  const [filtered, setFiltered] = useState(users);

  const handleChange = ({ target: { value } }) => {
    setSearchTerm(value);
    setFiltered(users.filter((item) => item.name.includes(value)));
  };

  return (
    <div className="container">

      <div>
        {
          users.length !== filtered.length 
           ? `${filtered.length} matches` 
           : null 
        }
      </div>

        <input 
            onChange={handleChange} 
            value={searchTerm} 
            type="text" 
            placeholder="Type a name"/>

        <div className="cards">
            {filtered.map((user) => (
               <div class="card">
                 <div className="profile">
                   <img 
                     src={user.avatar} 
                     alt="avatar" />
                 </div>
                 <div className="body">
                   <strong>{user.name}</strong>
                 </div>
               </div>
            ))}
        </div>

    </div>
  );
}

To break it down:

  • We have a couple of states searchTerm and filtered. The searchTerm state helps manage the text user enters in the textbox to filter the user list. The filtered is the state variable for managing the filtered user list. Please note we have initialized the filtered state with all users so that we can show all the users initially.

  • The JSX part is divided into three sections.

    • A message that shows the matched user count with the search term.

    • The textbox where the user inputs the search term.

    • A list of cards to show each of the user details.

  • We have added a change handler function to the search box so that we can handle the event every time someone types on it. In our case, it is the handleChange function.

  • The handleChange function is our main focus area! We update both the state values here. The first state update will update the search term value on the search textbox. The second state update will update the user list.

Finally, the Problem

By default, all the state updates are urgent(of high priority) in React. Also, as both the state changes occur almost simultaneously, React will batch them and update them together. It is a smart move because the component re-renders when the state update takes place, and too many renders will make the application experience bad.

But even with this smartness, we have a problem here! What if we are dealing with a huge list of users? The first state change will take a very short time to update the value of the search text box, but the second one may take longer to filter out a huge list of users.

ReactJS considers all state updates urgent by default, and these state changes occur simultaneously to batch the updates. Hence the quicker state update will wait for the longer one to complete. This may lead to a situation where the typing in the search textbox may feel sluggish and lagging which is not a great user experience!

The Solution: Meet the useTransition Hook

The useTransition hook is included in React 18 to tackle this situation. With this hook, you can specify any state updates as non-urgent. These non-urgent state updates will occur simultaneously with other urgent state updates, but the component rendering will not wait for the non-urgent state updates.

Let's look at the useTransition hook's anatomy, and then we will dive deeper into it.

Anatomy of useTransition

The useTransition hook returns an array with two elements:

  • isPending: The first one is a boolean-type variable that returns true when the second element startTransition function executes. It returns false when the execution of the startsTranstion function is complete.

  • startTransition: A function that takes a callback function as an argument. This callback function should contain code related to the non-urgent state update.

Going back to our application, the state change of the search textbox must be urgent because you want to see what you type in the textbox immediately. However, filtering the user list can be a non-urgent activity to defer a bit, and it will not cause much harm to the application's usability.

Let's now modify our application code using the useTransition hook.

How to Improve Usability and Performance Using the useTransition Hook?

The first thing is to import the hook to use it.

const [isPending, startTransition] = useTransition();

Next, we need to modify the handleChange method where we set the states for updates. Now we are marking the state change of the user-filtered list as non-urgent by placing it inside the callback function of startTransition.

const handleChange = ({ target: { value } }) => {
    setSearchTerm(value);
    startTransition(() => {
      setFiltered(users.filter((item) => item.name.includes(value)));
    });
};

The last thing we want to do is to use the isPending variable to ensure we show a loading indicator when the startTransition function executes.

{isPending ? (
   <div>Loading...</div>
   ) : (
   <div className="cards">
     {filtered.map((user) => (
       <div class="card">
         <div className="profile">
           <img 
             src={user.avatar} 
             alt="avatar" />
          </div>
          <div className="body">
            <strong>{user.name}</strong>
          </div>
       </div>
      ))}
   </div>
 )}

That's it! Now, the component is improved to handle the non-urgent state updates and component rendering much more efficiently.

Here goes the complete code:

import React, { useState, useTransition } from 'react';

export default function App({ users }) {
  const [searchTerm, setSearchTerm] = useState('');
  const [filtered, setFiltered] = useState(users);
  const [isPending, startTransition] = useTransition();

  const handleChange = ({ target: { value } }) => {
    setSearchTerm(value);
    startTransition(() => {
      setFiltered(users.filter((item) => item.name.includes(value)));
    });
  };

  return (
    <div className="container">

      <div>
        {
          isPending ? (
            <div>Loading...</div>
          ) : (
            <p>
              {
                 users.length !== filtered.length 
                 ? `${filtered.length} matches` 
                 : null
              } 
            </p>
          )
        }
      </div>

      <input 
        onChange={handleChange} 
        value={searchTerm} 
        type="text"
        placeholder="Type a name" />

      {
        isPending ? (
          <div>Loading...</div>
        ) : (
          <div className="cards">
            {filtered.map((user) => (
              <div class="card">
                <div className="profile">
                  <img 
                    src={user.avatar} 
                    alt="Avatar" />
                </div>
                <div className="body">
                  <strong>{user.name}</strong>
                </div>
              </div>
            ))}
          </div>
        )}
    </div>
  );
}

Source Code

All the source code used in this article are on this GitHub repository.

https://github.com/atapas/youtube/tree/main/react/20-useTransition

Btw, Do you want to see our app's behaviour with and without the useTransition hook? Check out this video section.

What's Next?

Next, practice! Try to find where you can use this hook to optimize application performance and increase usability and responsiveness. However, a word of caution is, don't overdo it. Every performance optimizations come with a cost. If you try to opt for it every time, it may be counter-intuitive for your application.

You need to find the use cases where you can mark state updates as non-urgent, thus delaying the component rendering against the more urgent state updates. Feel free to post your questions and doubts below as comments, I'll be glad to clarify them.

Here is something for you:

Learn React, Practically

I've been developing professional apps using ReactJS for many years now. My learning says you need to understand ReactJS fundamentally, under the hood to use it practically.

With that vision, I have created a ReactJS PlayLIst that is available for the developer community for FREE. Take a look:


Before We End...

Thanks for reading it. I hope it was insightful. If you liked the article, please post likes and share it in your circles.

Let's connect. I share web development, content creation, Open Source, and career tips on these platforms.