Explain React 18 useTransition Hook with Examples
Tapas Adhikary
Posted on May 9, 2023
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.
As we start typing a user name in the textbox to find the user, the user list below gets filtered like this:
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
andfiltered
. ThesearchTerm
state helps manage the text user enters in the textbox to filter the user list. Thefiltered
is the state variable for managing the filtered user list. Please note we have initialized thefiltered
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.
The useTransition
hook returns an array with two elements:
isPending
: The first one is a boolean-type variable that returns true when the second elementstartTransition
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.
Posted on May 9, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.