What is React's useTransition
Hook?
The useTransition
hook in React allows you to mark certain state updates as "transitions," which means they can be performed in the background without blocking the main UI thread. This helps keep your app responsive during potentially slow or non-urgent updates, like loading new content or switching views.
It returns an array with two values:
-
isPending
: A boolean that indicates if there's an ongoing transition (true while the update is pending). -
startTransition
: A function you wrap around your state update to mark it as a transition.
You call useTransition
at the top level of your component, similar to other hooks like useState
.
Why Use useTransition
?
The main purpose is to improve user experience by preventing the UI from freezing or showing unwanted loading states during updates that aren't critical. For example:
- In scenarios like tab switching, searching, or navigating pages, you want immediate feedback (e.g., the tab highlights right away) while the new content loads in the background.
- Without it, heavy updates could block interactions, making the app feel sluggish.
- It avoids interrupting urgent updates (like typing in an input) with less important ones.
- Transitions can be interrupted if a more urgent update comes along, ensuring the app prioritizes responsiveness.
Key benefits include maintaining smooth interactions, reducing perceived latency, and providing a way to show pending states (via isPending
) without global loaders.
However, it's not for everything: Transitions can't control text inputs directly, and multiple transitions are batched together (a current limitation).
Async Task Example with useTransition
Here's a focused example of using React's useTransition
with an async task: fetching search results based on user input. This keeps the UI responsive during the async fetch (e.g., no input lag while typing), and uses isPending
to show a loading indicator.
import { useState, useTransition } from 'react';
// Simulated async API call
async function fetchSearchResults(query) {
// Simulate a network delay of 1 second
await new Promise(resolve => setTimeout(resolve, 1000));
// Mock results based on query
return query ? [`Result for "${query}" 1`, `Result for "${query}" 2`] : [];
}
export default function SearchWithTransition() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleSearch = (newQuery) => {
setQuery(newQuery);
startTransition(async () => {
const fetchedResults = await fetchSearchResults(newQuery);
setResults(fetchedResults);
});
};
return (
<div>
<input
type="text"
value={query}
onChange={(e) => handleSearch(e.target.value)}
placeholder="Type to search..."
/>
{isPending && <p>Loading results...</p>}
<ul>
{results.map((result, index) => (
<li key={index}>{result}</li>
))}
</ul>
</div>
);
}
How It Works
-
Input Handling: As the user types,
handleSearch
updates the query state immediately (urgent update, so no lag in the input field). -
Async Fetch in Transition: The async fetch and results update are wrapped in
startTransition
, marking them as non-urgent. This allows React to keep the UI interactive during the delay. -
Pending State:
isPending
istrue
while the transition (fetch + update) is in progress, showing "Loading results...". - Responsiveness: If the user types quickly, ongoing transitions can be interrupted by new ones, prioritizing the latest input without blocking.
This is useful for debounced searches or any async updates where you want to avoid UI freezes. For real apps, replace the simulated fetch with an actual API call like fetch('/api/search?q=' + query)
.
Top comments (1)
Very well explained!๐