How to Use useTransition Hook in React
In this article, you will learn how to implement the
useTransition hook in React to improve application
performance and user experience. We will cover what the hook does, when
to use it, and provide a clear, step-by-step code example demonstrating
how to defer slow state updates so your user interface remains highly
responsive.
What is the useTransition Hook?
The useTransition hook is a React Hook introduced in
React 18 that lets you update the state without blocking the user
interface. It allows you to mark certain state updates as “transitions”
(non-urgent updates), which React can interrupt if a more urgent event
occurs, such as a user typing in an input field or clicking a
button.
The hook returns an array with exactly two values: 1.
isPending: A boolean flag that is true while
the transition is processing. 2. startTransition: A
function that lets you wrap a state update to mark it as a
transition.
Basic Syntax
const [isPending, startTransition] = useTransition();Step-by-Step Implementation
To implement useTransition, follow this practical
example of a search input that filters a large list of items. Without
useTransition, typing in the input would feel sluggish
because the app tries to render the heavy list on every keystroke.
Step 1: Import the Hook
Import useTransition alongside useState
from React.
import { useState, useTransition } from 'react';Step 2: Initialize the Hook
Call the hook inside your functional component.
const [isPending, startTransition] = useTransition();
const [inputValue, setInputValue] = useState('');
const [list, setList] = useState([]);Step 3: Wrap Non-Urgent Updates
Separate urgent updates from non-urgent ones. Updating the text input
value is urgent and must happen immediately. Updating the filtered list
is heavy and can be deferred using startTransition.
const handleChange = (e) => {
// Urgent: Update the input value immediately
setInputValue(e.target.value);
// Non-Urgent: Wrap the heavy list generation in startTransition
startTransition(() => {
const heavyList = [];
for (let i = 0; i < 20000; i++) {
heavyList.push(`${e.target.value} - Item ${i}`);
}
setList(heavyList);
});
};Step 4: Use isPending to Show a Loading State
Utilize the isPending boolean to provide visual feedback
to the user while the non-urgent state transition is being
processed.
return (
<div>
<input type="text" value={inputValue} onChange={handleChange} />
{isPending ? (
<p>Loading results...</p>
) : (
<ul>
{list.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
)}
</div>
);Complete Code Example
Here is how the entire component looks when put together:
import React, { useState, useTransition } from 'react';
export default function SearchApp() {
const [isPending, startTransition] = useTransition();
const [inputValue, setInputValue] = useState('');
const [list, setList] = useState([]);
const handleChange = (e) => {
setInputValue(e.target.value);
startTransition(() => {
const heavyList = [];
for (let i = 0; i < 20000; i++) {
heavyList.push(`${e.target.value} - Item ${i}`);
}
setList(heavyList);
});
};
return (
<div style={{ padding: '20px' }}>
<h2>Search Filter</h2>
<input
type="text"
value={inputValue}
onChange={handleChange}
placeholder="Type to filter..."
/>
{isPending && <p style={{ color: 'blue' }}>Updating list...</p>}
<ul>
{list.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
}Key Considerations
- Do not use it for controlled inputs: You cannot
wrap
setInputValue(the input’s own state update) instartTransition, as the input needs to update synchronously to reflect the user’s keystrokes. - Synchronous functions only: The function passed to
startTransitionmust be synchronous. If you need to perform asynchronous operations, you must handle the state update synchronously inside the resolved promise.