DesignDev.io logoDesignDev.io logo

  1. Home
  2. /React Foundations
  3. /How useDeferredValue and useTransition Work (With Real Examples)
← Back to home

How useDeferredValue and useTransition Work (With Real Examples)

Alex Chen

Alex Chen

Cover Image for How useDeferredValue and useTransition Work (With Real Examples)
Alex Chen

Alex Chen

January 6, 2026·4 min read
Updated April 26, 2026
Series:React Foundations
Level:Intermediate
Tags:reactweb
Assumes:Solid React hooks knowledge, familiar with useState and re-renders

React 18 shipped two hooks for keeping UIs responsive under load. Most explanations make them sound interchangeable. They're not — they solve related but distinct problems. Here's the difference, with real components that demonstrate exactly when each one matters.


React 18 introduced concurrent rendering — the ability to interrupt, pause, and prioritise render work so that urgent updates don't get blocked by expensive ones. useDeferredValue and useTransition are the two hooks that give you access to this from inside your components.

They solve the same category of problem: a state update that triggers an expensive render is making the UI feel sluggish. But they approach it differently, and using the wrong one for the job produces code that's harder to understand without solving the actual problem.

Let's start with the problem they both address.


The sluggish UI problem

You have a search input. As the user types, you filter a large list. The filtering is expensive — say, 2000 items with a complex matching function. Every keystroke triggers a re-render that takes 80ms. The input feels laggy because React is busy re-rendering the list before it can update the input.

The input update and the list render are treated with equal priority. React finishes the expensive list render before it can process the next keystroke. The result: a UI that feels like it's fighting you.

Both hooks fix this. But they do it at different layers.


useTransition: wrap the state update

useTransition lets you mark a state update as non-urgent. React will process urgent updates first — keeping the input responsive — and handle the transition update when it has time.

Two state values, two update priorities. query updates immediately — the input is always responsive. deferredQuery updates in a transition — the list re-renders when React has a gap between higher-priority work.

isPending is the bonus: a boolean that's true while the transition is in progress. Use it to show a subtle loading indicator without blocking the UI.


useDeferredValue: wrap the value

useDeferredValue works at the value level rather than the update level. You pass it a value and it returns a deferred copy — one that lags behind the real value during expensive renders.

One state value. query updates synchronously on every keystroke — the input is responsive. deferredQuery is a copy of query that React updates at lower priority. The list renders with the deferred value, so expensive list renders don't block the input.

No startTransition. No separate state. Just wrap the value and let React handle the prioritisation.


The real difference

Both approaches solve the same problem. The difference is where you apply the solution.

Use useTransition when you control the state setter. You're the one calling setState — you can wrap that call in startTransition. This is the right tool when the state update and the expensive render are in the same component or when you want the isPending flag.

Use useDeferredValue when you don't control the state setter. The state lives elsewhere — in a parent, in a library, in a URL parameter. You receive a value as a prop and need to defer the expensive work that depends on it. You can't wrap someone else's setState in a transition, but you can defer the value you receive.

In the second example, ResultsList has no control over when query changes — it just receives it. useDeferredValue lets it opt into lower-priority rendering for the expensive computation.


Building something real: a live search with both hooks

Here's a complete live search component that demonstrates both hooks working together — useTransition in the parent for the isPending indicator, useDeferredValue in the results component for the expensive filtering.

Three things working together here:

useTransition in LiveSearch — the query state update is wrapped in startTransition. The input is uncontrolled (onChange without value) so it updates natively at full speed. React processes the state update at lower priority. isPending shows a spinner while the transition is in progress.

useDeferredValue in ResultsList — receives query as a prop and defers it. The filtering runs against deferredQuery, not the latest query. While the deferred value is catching up, isStale is true and the list fades to 60% opacity — a visual cue that results are updating.

React.memo on ResultsList — without this, ResultsList re-renders every time the parent renders, regardless of whether query changed. memo ensures it only re-renders when its props change, which makes the useDeferredValue deferral actually meaningful.


What these hooks are not

They are not debounce. Debounce delays an update by a fixed time. Transitions interrupt when urgent work arrives. They feel similar but behave differently — a 300ms debounce always waits 300ms. A transition waits as long as React needs to, then catches up.

They are not a solution to slow network requests. Transitions affect render priority, not network timing. A slow API call is still slow. Use TanStack Query's keepPreviousData option for the pattern of showing stale results while a new fetch completes.

They are not always necessary. If your expensive render takes under 50ms, users won't notice the difference. Profile before optimising. The React DevTools Profiler shows render durations per component — use it to confirm you have a real problem before adding concurrent hooks.


The decision rule

When in doubt, reach for useDeferredValue first. It's less code and handles the common case. Add useTransition when you need isPending or when you're coordinating multiple state updates that should all transition together.


Up next: The complete guide to React error boundaries in 2026 — what they catch, what they miss, and the pattern that makes them actually useful in production.

Related: Why React.memo doesn't always help — and when it does — the companion article on memoization that pairs naturally with transitions.

Alex Chen is a senior frontend engineer who writes about React patterns, JavaScript internals, and the decisions that separate maintainable codebases from ones that fight back. Opinionated by design.

Photo by Victoria Paar on Unsplash


More Stuff

Publishing Your First React Component to npm — the Complete Setup
April 1, 2026·3 min read

Publishing Your First React Component to npm — the Complete Setup

Most tutorials stop at "it works on my machine." This one doesn't. Publishing to npm is where your library becomes real — installable, versioned, and usable by anyone. Here's the complete setup, including the fields most developers get wrong in package.json and how to inspect what you're actually shipping before it goes live.

Muhammad Athar

Muhammad Athar

How to structure a UI library that scales — folders, exports, and naming conventions
April 1, 2026·3 min read

How to structure a UI library that scales — folders, exports, and naming conventions

The folder structure you pick on day one is the one you'll live with across 40 components. Get it wrong and you'll be fighting your own codebase by article 10. Get it right and adding a new component takes five minutes and follows a pattern every contributor can learn in one sitting.

Muhammad Athar

Muhammad Athar

DesignDev.io logoDesignDev.io logo

Explore

  • Home
  • Search
  • Authors
  • Series
  • About Us

Account

  • Sign in

Legal

  • Privacy Policy
  • Terms of Service

© 2026 DesignDev.io · All rights reserved

Built with Next.js · Tailwind · Sanity · Vercel