useTransition and useDeferredValue in React
Concurrent Features in React 18+
React 18 introduced concurrent rendering with two key hooks: useTransition and useDeferredValue. They allow you to mark certain state updates as non-urgent, keeping the UI responsive.
useTransition
useTransition lets you mark a state update as a transition โ a non-urgent update that can be interrupted by more urgent updates (like user input).
import { useState, useTransition } from "react";
function SearchPage() {
const [query, setQuery] = useState("");
const [results, setResults] = useState<string[]>([]);
const [isPending, startTransition] = useTransition();
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
// โ
Urgent: update input immediately
setQuery(value);
// โ
Non-urgent: update results in a transition
startTransition(() => {
const filtered = heavyFilter(value); // expensive operation
setResults(filtered);
});
};
return (
<div>
<input value={query} onChange={handleChange} />
{isPending && <Spinner />}
<ResultList results={results} />
</div>
);
}How useTransition Works
isPendingโ boolean flag,truewhile the transition is ongoingstartTransition(callback)โ wraps the non-urgent state update- React can interrupt the transition if a more urgent update comes in
- The input stays responsive because the heavy update is deferred
useDeferredValue
useDeferredValue creates a deferred copy of a value that "lags behind" the original. React updates it only when it has spare capacity.
import { useState, useDeferredValue, useMemo } from "react";
function SearchResults({ query }: { query: string }) {
// Creates a deferred version of query
const deferredQuery = useDeferredValue(query);
const isStale = query !== deferredQuery;
// Only re-computes when deferredQuery changes
const results = useMemo(
() => heavySearch(deferredQuery),
[deferredQuery]
);
return (
<div style={{ opacity: isStale ? 0.5 : 1 }}>
{results.map(r => <div key={r.id}>{r.title}</div>)}
</div>
);
}
function App() {
const [query, setQuery] = useState("");
return (
<div>
<input value={query} onChange={e => setQuery(e.target.value)} />
<SearchResults query={query} />
</div>
);
}useTransition vs useDeferredValue
| Feature | useTransition | useDeferredValue |
|---|---|---|
| What it defers | A state update (setState) | A value |
| Where to use | When you own the state setter | When you only have the value (props) |
| Returns | [isPending, startTransition] | Deferred value |
| Pending indicator | Built-in isPending | Compare original vs deferred |
When to Use Which
// useTransition โ you control the setState
const [isPending, startTransition] = useTransition();
startTransition(() => {
setFilteredList(expensiveFilter(items));
});
// useDeferredValue โ you receive the value from parent
function ChildComponent({ searchTerm }: { searchTerm: string }) {
const deferredTerm = useDeferredValue(searchTerm);
// Use deferredTerm for expensive rendering
}Real-World Use Cases
Tab Switching
function TabContainer() {
const [tab, setTab] = useState("home");
const [isPending, startTransition] = useTransition();
function selectTab(nextTab: string) {
startTransition(() => {
setTab(nextTab); // Heavy tab content can render in background
});
}
return (
<div>
<TabButtons onSelect={selectTab} activeTab={tab} />
{isPending ? <Spinner /> : <TabContent tab={tab} />}
</div>
);
}Large List Filtering
function FilterableList({ items }: { items: Item[] }) {
const [filter, setFilter] = useState("");
const deferredFilter = useDeferredValue(filter);
const filtered = useMemo(
() => items.filter(item => item.name.includes(deferredFilter)),
[items, deferredFilter]
);
return (
<>
<input value={filter} onChange={e => setFilter(e.target.value)} />
<ul style={{ opacity: filter !== deferredFilter ? 0.7 : 1 }}>
{filtered.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
</>
);
}Important:
useTransition and useDeferredValue are React 18+ features for keeping UI responsive during expensive updates. Use useTransition when you control the state setter, and useDeferredValue when you receive a value as a prop. Both are about prioritizing urgent updates (user input) over non-urgent ones (search results, heavy renders).
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.