Navigate
Search topics, hooks, and fundamentals
What if heavy renders never jammed your ER's emergency hotline?
π₯ Theme: Emergency Room
Your UI is a hospital ER. Without triage, every update β typing in the search box, rendering 10,000 patient records β fights for the same thread. The hotline freezes while records load. useTransition is the triage nurse: it keeps the hotline (urgent updates like typing) open while heavy patient records process in the background.
Without useTransition, every keystroke blocks the main thread. React must finish filtering all 10,000 patient records before it can update the input β the emergency hotline goes dead.
Emergency Hotline
Try typing fast β the hotline can't keep up!
...and 9,800 more
useTransition acts as the triage nurse. Urgent updates β like the hotline input β are handled immediately. Heavy record filtering is marked non-urgent and routed to the waiting room, leaving the hotline free.
Emergency Hotline
Type fast β the hotline stays responsive!
...and 9,800 more
function TriageSystem() {
const [query, setQuery] = useState("");
const [triageQuery, setTriageQuery] = useState("");
const [isPending, startTransition] = useTransition();
function handleCall(value: string) {
setQuery(value); // Urgent: update hotline immediately
startTransition(() => {
setTriageQuery(value); // Non-urgent: route to waiting room
});
}
const records = PATIENTS.filter(p => p.includes(triageQuery));
return (
<>
<input value={query} onChange={e => handleCall(e.target.value)} />
<div style={{ opacity: isPending ? 0.6 : 1 }}>
{records.map(p => <div key={p}>{p}</div>)}
</div>
</>
);
}useTransition is your ER's triage nurse β it keeps the hotline open while heavy patient records process in the background.
useTransition shines wherever a state update triggers expensive rendering. Explore the ER β search records, run ward rounds, and decide when triage is worth calling in.
...and 4,900 more
const [query, setQuery] = useState("");
const [searchQuery, setSearchQuery] = useState("");
const [isPending, startTransition] = useTransition();
function handleLookup(value: string) {
setQuery(value); // instant input update
startTransition(() => {
setSearchQuery(value); // deferred search
});
}
// isPending? Fade the grid while records catch up.Three wards, 3,000 records each. Toggle triage mode and switch wards β feel the difference.
const [isPending, startTransition] = useTransition();
const [activeWard, setActiveWard] = useState("emergency");
function switchWard(ward: string) {
if (triageMode) {
startTransition(() => setActiveWard(ward));
} else {
setActiveWard(ward); // blocks until records render
}
}Answer these questions to decide if useTransition is worth it.
Does the state update trigger expensive rendering (large list, complex computation)?
Is there a separate urgent update that must stay responsive (input, animation)?
Can users tolerate seeing stale content briefly while the update processes?
// DO triage β heavy filter blocks the input
const [isPending, startTransition] = useTransition();
startTransition(() => setFilteredResults(expensiveFilter(query)));
// DO triage β tab switch renders 3,000 items
startTransition(() => setActiveTab(newTab));
// DON'T β simple state update, fast render
setCount(c => c + 1); // no transition neededThese are the patterns that trip up developers most often. Switch between Wrong and Fixed to compare the code side by side.
function Search({ items }) {
const [query, setQuery] = useState("");
const [filtered, setFiltered] = useState(items);
const [isPending, startTransition] = useTransition();
const onChange = (e) => {
startTransition(() => {
setQuery(e.target.value); // urgent β should NOT be here
setFiltered(expensiveFilter(items, e.target.value));
});
};
// Input feels laggy because the urgent update is deferred too
return <input value={query} onChange={onChange} />;
}function UserList() {
const [users, setUsers] = useState([]);
const [isPending, startTransition] = useTransition();
const loadUsers = () => {
startTransition(() => {
fetch("/api/users") // async β transition ends immediately
.then(res => res.json())
.then(data => setUsers(data)); // isPending already false
});
};
return <button>{isPending ? "Loading..." : "Load"}</button>;
}function TabPanel() {
const [tab, setTab] = useState("home");
const [isPending, startTransition] = useTransition();
return (
<button onClick={() => startTransition(() => setTab("analytics"))}>
Analytics
</button>
// User sees nothing during the transition β feels broken
);
}