What does useTransition() do?
What useTransition() does
useTransition()lets you mark part of a state update as a "transition" - that is, a lower-priority update that React can defer in favor of more important ones (such as text input, clicks, animations).
In simpler terms:
This is a way to tell React: "This update is not urgent - you can do it a bit later, once you're done with the fast stuff."
Example without useTransition
function Search() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
function handleChange(e) {
const value = e.target.value;
setQuery(value);
// Emulate heavy filtering
const filtered = hugeList.filter(item => item.includes(value));
setResults(filtered);
}
return (
<>
<input value={query} onChange={handleChange} />
<ul>{results.map(r => <li key={r}>{r}</li>)}</ul>
</>
);
}The problem: when typing fast, React re-renders the huge list on every character, which makes the input "lag" - the cursor stutters and updates slow down.
1. The solution with useTransition
import { useState, useTransition } from "react";
function Search() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
function handleChange(e) {
const value = e.target.value;
setQuery(value); // update the input right away (high priority)
startTransition(() => {
// update the results later (low priority)
const filtered = hugeList.filter(item => item.includes(value));
setResults(filtered);
});
}
return (
<>
<input value={query} onChange={handleChange} />
{isPending && <p>Loading...</p>}
<ul>{results.map(r => <li key={r}>{r}</li>)}</ul>
</>
);
}What React does:
setQuery()runs immediately → the input field stays responsive;- React defers
setResults()- this is a "transition update"; - While the transition runs →
isPending = true(you can show a loader); - When React finishes the transition,
isPendingresets.
The UI stays smooth, typing is instant, the list updates a bit later, but without lag.
What happens "under the hood"
React now has two update priorities:
| Update type | Example | Priority |
|---|---|---|
| Urgent | Text input, clicks, scrolling | Runs immediately |
| Transition | Filtering, loading data, switching tabs | Can be paused |
When you call startTransition(fn):
- React puts the updates inside
fninto a low-priority queue; - it processes urgent updates first (for example, rendering the input);
- then, when it's "free", it runs the transition;
- if the user does something during the transition → React can interrupt the transition and start over (without freezing).
2. Asynchronous scenarios: API requests
import { useState, useTransition, Suspense } from 'react';
import useSWR from 'swr';
const fetcher = url => fetch(url).then(r => r.json());
function SearchResults({ query }) {
const { data } = useSWR(`/api/search?q=${query}`, fetcher, { suspense: true });
return <ul>{data.results.map(r => <li key={r}>{r}</li>)}</ul>;
}
export function Search() {
const [query, setQuery] = useState('');
const [displayQuery, setDisplayQuery] = useState('');
const [isPending, startTransition] = useTransition();
function handleChange(e) {
const value = e.target.value;
setQuery(value);
startTransition(() => setDisplayQuery(value)); // asynchronous transition
}
return (
<>
<input value={query} onChange={handleChange} />
{isPending && <p>Loading...</p>}
<Suspense fallback={<p>Loading results...</p>}>
<SearchResults query={displayQuery} />
</Suspense>
</>
);
}What the code does:
- the user types fast → React updates the input instantly;
displayQueryupdates "deferred" →SWRtriggers a fetch;- until the data arrives → React shows the
Suspensefallback; - everything is smooth, without blocking the interface.
3. The values useTransition() returns
const [isPending, startTransition] = useTransition();| Variable | Description |
|---|---|
isPending | true while the transition is running (you can show a loader / disable a button) |
startTransition(cb) | runs a function as a "deferred" state update |
4. The difference from useDeferredValue()
| Hook | What it does | When to use it |
|---|---|---|
useTransition() | defers running a state update | when you set state (setState) |
useDeferredValue() | defers using a value | when you pass a value into a heavy component |
Example:
// useTransition
startTransition(() => setQuery(value)); // update state deferred
// useDeferredValue
const deferredQuery = useDeferredValue(query); // use the value deferred5. Real-world cases
| Scenario | How it helps |
|---|---|
| Searching a large list | instant typing, filtering deferred |
| Switching between tabs | the old tab stays until the new one is ready |
| Updating tables / charts | smooth UI while filtering |
| Navigating to another page (SPA) | React shows the "old" page until the new one loads |
| Suspense + API | background data loading without blocking |
6. What happens visually
Without useTransition | With useTransition |
|---|---|
| The UI freezes while typing | The UI is responsive |
| New content replaces the old one instantly (flickers) | The old content stays until the new one is ready |
| The transition feels "harsh" | The transition is smooth and natural |
SUMMARY
| What it does | Why it matters |
|---|---|
| Defers "heavy" updates | The UI stays responsive |
| Separates priorities | React doesn't block input and clicks |
Returns isPending | you can show an indicator |
| Works with Suspense | smooth async transitions |
| Simplifies UX with large data | filters, search, sorting |
In short:
useTransition()is a way to tell React: "These updates aren't urgent - do them when you're free."It makes the UI instantly responsive, even when there is asynchronous data loading or a heavy render happening under the hood.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.