The React useTransition hook helps to update the state without blocking the UI, allowing high-level responsiveness!
Small actions (clicking a button or typing into an input) can sometimes trigger a lot to happen on screen, and can quickly freeze or hang a UI while your app completes the work.
React useTransition hook keeps your React 18 app responsive, controlling the order of state changes. Render a UI (user interface) update you need and block others
What Is useTransition Hook? What Is Transition?
A new hook in React 18, useTransition, will help your React applications stay responsive between rerenders and user interactions. The API allows you to batch state changes (“Transitions“) so that React can pause or interrupt less critical (“Non-urgent“) of them.
Usually, you want immediate updates for live or urgent changes (typing, clicking, pressing) while delaying transitions of UI views.
With the useTransition hook, we can delay the re-rendering of a component until it meets certain conditions. useTransition() prevents unwanted re-renders or waits for any asynchronous operation before React renders components
import { useTransition } from 'react'; const [isPending, startTransition] = useTransition();
useTransition returns startTransition (function) and isPending (boolean flag). Wrap less critical state updates (setState()) inside the startTransition() callback function to run them in the background while keeping the UI responsive.
Some devs use the setTimeout API in similar use cases, but it blocks the UI (unfortunately) if the updates are heavy. The setTimeout pattern delays the execution of your code, whereas startTransition() executes immediately and marks the updates for non-urgent processing.
(New to React? Read my post: “How To Set Up React App With Vite“)
How React 18 Concurrent Mode Affects Responsiveness in React App
Concurrent Mode makes rendering interruptible to keep the UI responsive during heavy updates. React can now:
- Drop working on rendering a component
- Execute more critical tasks
- And return to rendering!
This mode lets React delay low-priority and prioritize urgent updates!
For example, using useTransition (or useDeferredValue) allows you to tell React which useState() to delay. If a component “React.Suspends” with a concurrent render, it stays in its current state and waits for resolution, NOT showing a fallback UI.
UseTransition hook handles both concurrency and suspense.
Concurrent React pauses in the middle of the render, deals with critical concurrent matters, and then restarts right where it left off with the “not-so-important” stuff.
Learn React useTransition Hook: Cooking Analogy
Imagine making a dish in the kitchen with several preparation steps. You’re currently in the middle of a sauce simmering (Non-urgent transaction) task.
Suddenly, you see that the main dish is about to boil over (Urgent task) — you stop simmering the sauce, turn off the gas, and remove the lid!!
In terms of JavaScript, React and useTransition
- Preparing a main course is like updating the DOM and rendering it
- A simmering sauce is a low-priority (Non-urgent) state update
- Boil-prevent actions are a high-priority (Urgent) state update
You must QUICKLY address the boil-over situation, yet at the same time, you cant withdraw your attention entirely from the sauce simmering.
/* Your kitchen tasks */ // Handle the urgent task handleBoilOver(); // setHighPriorityState() // After handling the Urgent task, resume the transaction/original task startTransition(() => { // Callback function // Lower the priority of the sauce simmering simmerSauce(); // setLowPriorityState() });
With startTransition(() => { … }), you can temporarily lower the priority of simmering the sauce, allowing React (or you in the analogy) to focus on the urgent, high-priority task. Once React handles the urgent task, it smoothly returns to your original task.
Another new React useDeferredValue() hook allows you to run side effects as low-priority tasks (Concurrent Mode) while the main task remains smooth.
(Speaking of food — read my post about “What Is Spaghetti Code And How To Avoid It!“)
Use React useTransition Hook For Slow State Update Of Huge List
Filtering a list of data while typing in an input field is a performance challenge, especially for complex lists or items. To curate such a list, we need to update both the input value and the filtered results, which causes noticeable lag and disrupts UX (user experience).
startTransition helps you differentiate between urgent (displaying the input value) and non-urgent (filtering and re-rendering the list) updates you can defer.
import { startTransition } from 'react'; // URGENT: Show what user types setInputValue(input); // Mark any state updates inside as transitions startTransition(() => { // TRANSITION: Filter and render the results setFilteredItems(items.filter()); });
Now, you can inform your user that a background process is in action with the isPending flag. Use it to indicate any pending Transition and display a loader, spinner, simple “Please wait” text, and so on.
import { useTransition } from 'react'; const [isPending, startTransition] = useTransition(); {isPending && <Spinner />}
Take, for example, my code sandbox with search inputs that filter text within that long list (I used several thousand to slow the render artificially).
Type numbers quickly, and then delete and play around with these inputs. Observe that in the Transition input, as we type, the field’s value is set right away, while React blocks the Regular input — you can’t set it to a new value until React renders the new filtered list.
With useTransition, If the user types again before React re-renders all items, it will interrupt rendering to update the search field value (with a setInputValue). When you stop typing, React will re-render the list and update items.
This approach reduces the need to debounce (and 3-rd party tools for debouncing) the search input to save performance.
useTransition and Suspense Use Case for Coding Software Developer
You can now request React 18 to render a stale component waiting for a newer, asynchronous one. While the new component fetches and prepares to run after the mount, you want to avoid it flickering or rendering with incorrect size.
“React.Suspense” allows you to define a fallback when initially loading data (e.g., navigating to a new page). This keeps a good UX (user experience) showing a loading spinner or some placeholder content while you fetch the data.
Suspense WITHOUT React useTransition Clears Existing UI
Imagine an application with two pages: Home and Profile. React.Suspense wraps your application inside a fallback UI boundary, as follows:
const [currentPage, setCurrentPage] = useState("home"); const navigate = (page) => { setCurrentPage(page); }; return ( <div> <h2>No Transition</h2> <Suspense fallback={<div>Global fallback...</div>}> <div> <nav> <button onClick={() => navigate("home")}>Home</button> <button onClick={() => navigate("profile")}>Profile</button> </nav> {currentPage === "home" ? <Homefeed /> : <Profile />} </div> </Suspense> </div> );
I’m using a custom lazyLoadComponent function (with React.lazy and setTimeout() under the hood) to simulate a 2-second delay in loading the component. When you first enter the page, you will see the “Global fallback…” for 2 seconds until the Homefeed component loads.
The problem is that when you load new data on a page that has already been loaded (e.g., navigate to Profile from Home), Suspense will clear the existing content (Home page) and the “Global fallback…” will show again while waiting for Profile data — confusing and disrupting UX.
Suspense WITH React useTransition Keeps Existing UI
In these cases, startTransition will prevent re-triggering Suspense boundaries and keep the existing UI while for a replacement.
const [isPending, startTransition] = useTransition(); const navigate = (page) => { startTransition(() => { setCurrentPage(page); }); };
Continue rendering (think of separate indicators or pale colors) the Home page until the Profile data is available without retriggering the fallback boundary. This makes it possible to render Suspense boundaries in the way you want— users won’t have to wait for all new data before they see any content.
For example, you can add a boundary around the friends list in the Profile so that users can view the rest of the page while the friends list loads.
Without Vs With React useTransition
Without useTransition | With useTransition |
|
|
Conclusion — Use useTransition For Application Responsiveness
useTransition() prioritizes urgent updates, defers non-urgent tasks, and creates a smoother UX (user experience). It remains your React apps performant even under a HEAVY load with experience minimal lag and maximum responsiveness.
useTransition is a powerful tool that a JavaScript web developer should master to build responsive applications — integrate this hook into your dev practice to optimize performance and UX.
(Read my other post about New Actions In React 19)