useTransition Explained: Make React Feel Instant
useTransition Explained
useTransition is a React 18+ concurrent hook that lets you mark some state updates as non-urgent.
This helps your app stay responsive by making React prioritize:
- ✅ urgent updates (typing, clicking, input responsiveness) over
- ⏳ slow UI updates (big list rendering, expensive filtering, heavy page transitions)
🧩 The Problem It Solves
Imagine a search input where filtering is heavy:
- User types: input updates
- At the same time, you render a huge list based on the text
Without useTransition, React treats both as equally urgent → typing can feel laggy.
⚙️ Basic API
import { useTransition, useState } from "react";
export default function SearchPage({ items }: { items: string[] }) {
const [query, setQuery] = useState("");
const [filter, setFilter] = useState("");
const [isPending, startTransition] = useTransition();
function onChange(e: React.ChangeEvent<HTMLInputElement>) {
const value = e.target.value;
// ✅ urgent: update input right away
setQuery(value);
// ⏳ non-urgent: update expensive list/filter
startTransition(() => {
setFilter(value);
});
}
const filtered = items.filter((x) => x.includes(filter));
return (
<div>
<input value={query} onChange={onChange} />
{isPending && <p>Updating results…</p>}
<ul>
{filtered.map((x) => (
<li key={x}>{x}</li>
))}
</ul>
</div>
);
}
What you get
startTransition(fn)marks updates inside as transition (low priority).isPendingbecomestruewhile React is working on those updates.
🧠 Mental Model
Think of React updates like a queue with priorities:
- Urgent lane: typing, clicks, pointer movement
- Transition lane: slow rendering that can wait
startTransition moves updates into the transition lane.
So the user experience becomes:
- input stays snappy ✅
- expensive rendering happens a bit later ⏳
🔍 How It Works Under the Hood
React Fiber + Scheduler does this:
setQuery(value)schedules an urgent update.startTransition(() => setFilter(value))schedules a low priority update.- If the user keeps typing, React may:
- pause the low priority render
- restart it with the latest value
- skip intermediate renders
That’s why transitions feel smooth: React avoids wasting work.
⚡ useTransition vs useDeferredValue
| Feature | useTransition | useDeferredValue |
|---|---|---|
| You control | a state update | a derived value |
| API style | Active (startTransition) | Passive (useDeferredValue(value)) |
| Pending indicator | ✅ isPending | ❌ no built-in pending |
| Best for | triggering work | delaying derived rendering |
| Typical use | page transitions, filtering | search input results, previews |
Rule of thumb
- Use useTransition when you want to say: “this update can wait.”
- Use useDeferredValue when you want: “render using an older value until you have time.”
✅ Great Use Cases
- Filtering large lists
- Switching tabs with heavy content
- Updating charts
- Navigating to a route that triggers lots of work
- Anything where “loading/pending” UX helps
⚠️ What useTransition is NOT
- ❌ It is not debounce/throttle
- ❌ It does not run work in a separate thread
- ❌ It doesn’t magically make slow code fast — it just makes the UI stay responsive
If your render is extremely slow, consider:
- memoization (
useMemo,React.memo) - virtualization (react-window)
- splitting components / code-splitting
- improving algorithms
🧱 Common Mistakes
1) Putting urgent updates inside transition
startTransition(() => {
setQuery(value); // ❌ makes typing laggy
});
Keep input updates urgent.
2) Creating new heavy objects on every render
If your filtered list is recomputed every render, also consider memoizing or pre-indexing.
🔁 isPending UX pattern
{isPending ? <Spinner /> : <Results />}
or keep results visible and show subtle hint:
{isPending && <small>Updating…</small>}
✅ Summary
useTransitionmarks updates as low priority.- It protects user input from being blocked by heavy renders.
- Powered by Fiber + Scheduler, React can pause/restart work.
- Pair it with good performance practices for best results.
✨ useTransition makes React apps feel instant by keeping “urgent UI” always responsive.