Cansu Arı Logo
Blog
How To Do?

useTransition, useDeferredValue, and Scheduling Management

Introduced in React 18, useTransition and useDeferredValue help minimize perceived delay during user interactions.

  • #React
  • #Performance

Skip to content

The smart way to manage React render timing

React kicked off a new era to reduce response time to user interactions.
With React 18 and Concurrent Rendering, renders are now prioritized instead of always being synchronous.
And in this world we have two new heroes: useTransition and useDeferredValue.
If you come from Vue.js, you’ll notice the mindset is similar to features like nextTick, Suspense, flush: 'post', or custom useDebounce/useThrottle/watchEffect patterns.

In this post, we’ll cover how these two hooks work, how they differ, and practical usage scenarios.


Primer: What is Concurrent Rendering?

Before React 18, each render was synchronous. Even while a user was typing, React might rebuild parts of the UI on every keystroke. Now React can assign priorities:
  • Urgent tasks → button clicks, typing in inputs.
  • Non-urgent tasks → filtering big lists, heavy computations, chart updates.
💡 The goal: never make user interactions feel “stuck.”

useTransition: Defer heavy renders

useTransition pushes long-running updates into the “background.” You’re telling React: “Don’t block the user; run this later.”
const [isPending, startTransition] = useTransition()

function handleChange(e) {
const value = e.target.value
startTransition(() => {
setFilter(value)
})
}

What’s happening?

  • The user types in the input.
  • React updates the input value immediately (urgent task).
  • The list filtering wrapped in startTransition is deferred (non-urgent task).
Result: the input stays responsive; the list updates in the background. 🪄

What does isPending do?

When isPending is true, React is still processing the deferred work.
{isPending && <span>Filtering…</span>}
You can use this to show subtle progress UI.

TL;DR: useTransition = "Defer the render while protecting UX."


Real-world example

function Search() {
const [query, setQuery] = useState('')
const [results, setResults] = useState([])
const [isPending, startTransition] = useTransition()

const handleChange = (e) => {
const value = e.target.value
setQuery(value)
startTransition(() => {
setResults(bigData.filter(item => item.includes(value)))
})
}

return (
<>
<input value={query} onChange={handleChange} placeholder="Search…" />
{isPending && <p>Searching…</p>}
<ul>
{results.map(r => <li key={r}>{r}</li>)}
</ul>
</>
)
}
The input updates instantly; filtering runs later. Net effect: a smooth, non-blocking search. ✅

useDeferredValue: Update a value later

useDeferredValue makes a value “lag” on purpose. Anything derived from that value will render when the system has time.
const deferredQuery = useDeferredValue(query)
const filteredList = bigData.filter(item => item.includes(deferredQuery))
Here query changes immediately, but deferredQuery updates slightly later. That keeps typing responsive.

useTransition vs useDeferredValue

TraituseTransitionuseDeferredValue
What it defersA state updateA value
Where it’s usedAround a state setterAround the value itself
Who controls timing?You (startTransition)React
Typical goalDefer heavy state changesDefer expensive derived computations
💡 In short: useTransition = defer the update; useDeferredValue = defer the value.

Used together

function FilterList({ items }) {
const [query, setQuery] = useState('')
const deferredQuery = useDeferredValue(query)
const [isPending, startTransition] = useTransition()

const filtered = items.filter(i => i.includes(deferredQuery))

return (
<>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Filter…"
/>
{isPending && <p>Updating…</p>}
<ul>
{filtered.map(f => <li key={f}>{f}</li>)}
</ul>
</>
)
}
  • query updates now.
  • deferredQuery catches up soon after.
  • The UI never feels blocked.

Why it matters

If every render ran with the same priority, UX would suffer—especially at scale. These hooks let React distinguish between:
  • “What the user sees/feels right now.”
  • “Work we can finish a bit later.”
The result is truly responsive interfaces.

Performance tips

  1. Wrap long-running updates in startTransition.
  2. Never block user interactions (typing, clicks).
  3. Show feedback with isPending.
  4. Use useMemo/useCallback to avoid unnecessary renders.

useTransition + Suspense = a powerful duo

With React 18, Suspense can coordinate with useTransition in data fetching flows.
startTransition(() => {
setResource(fetchData())
})
React marks the transition as low-priority and shows a loading state without freezing the UI.

💡 Pro tip: Transitions act like a small “UI state machine”: pending → reveal → update.


The invisible win

You might not notice a 200ms delay—but users do. useTransition and useDeferredValue make those delays vanish. Small, subtle improvements add up to big UX gains.

Conclusion

React 18+ is not just about “rendering”—it’s about prioritizing. These two hooks keep modern UIs fluid:
  • useTransition → Defer heavy state updates.
  • useDeferredValue → Defer derived values to protect responsiveness.
  • Use both → A UI that breathes and never stutters.
All tags
  • React
  • Performance