Using useTransition and useDeferredValue to Improve INP

This guide is part of React Rendering Performance, within Framework Performance. Some updates must feel instant: the character you typed appearing in the input, the checkbox toggling, the tab highlighting. Others can take a moment: the filtered list of 2,000 results, the newly selected tab's heavy content, a chart redrawing. In React's default synchronous rendering, both kinds of work happen together, and the slow part blocks the fast part — the input lags because the list is rendering.

React's concurrent features separate them. useTransition marks state updates as non-urgent: React renders them in the background, yielding to the browser so it can paint and handle input, and abandons a stale render if newer input arrives. useDeferredValue gives a lagging copy of a value: components using the deferred copy re-render at lower priority, while those using the current value update immediately. For INP, this means the interaction's next paint shows the urgent update quickly, even when the full update takes longer.

Filter click with and without a transition Timeline comparing a filter click that renders a large list synchronously with one that updates the button immediately and renders the list in a transition. Filter click with and without a transition Synchronous render button + 2,000 rows paint Transition button paint list in background (yields) 0ms 50ms 100ms 150ms 200ms 250ms 300ms 350ms 400ms next paint

Rapid Diagnosis

  • Find interactions with high INP where the handler sets state that drives heavy rendering.
  • Profile the interaction: a long commit dominated by a large subtree (lists, charts, tab content).
  • Check whether the urgent feedback is part of the same update (input value, selected state).
  • Check React version: concurrent features require React 18 or later with createRoot.

Root Cause Analysis

1. One update for urgent and non-urgent UI. Input value and results update together.

2. Large subtrees on each interaction. Lists and charts re-render synchronously.

3. No yielding. Synchronous renders block the main thread until complete.

4. Stale work. Fast typing triggers renders for intermediate values that are immediately outdated.

Step-by-Step Resolution

1. Wrap non-urgent updates in startTransition

javascript
function Filters({ onChange }) {
  const [active, setActive] = useState('all');
  const [isPending, startTransition] = useTransition();
  function select(value) {
    setActive(value);                                  // urgent: button state
    startTransition(() => onChange(value));            // non-urgent: heavy list update
  }
  return <FilterButtons active={active} onSelect={select} pending={isPending} />;
}
// trade-off: the list shows old results briefly; show isPending (dimmed list or
// spinner) so users know the update is in progress.

2. Defer values passed to expensive children

javascript
function SearchPage({ items }) {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  const isStale = query !== deferredQuery;
  return (<>
    <input value={query} onChange={(e) => setQuery(e.target.value)} />
    <div style={{ opacity: isStale ? 0.6 : 1 }}><Results items={items} query={deferredQuery} /></div>
  </>);
}
const Results = React.memo(function Results({ items, query }) { /* heavy filtering + rendering */ });
// trade-off: Results must be memoized, or it re-renders with the urgent update too.

3. Use transitions for tab and route changes

Wrap tab switches and navigation state changes in transitions so the old content stays interactive while the new content renders.

4. Make the heavy render cheaper too

Transitions defer work; combine them with memoization, virtualization and moving expensive computation out of render.

useTransition vs useDeferredValue Comparison of the two React concurrent features for keeping interactions responsive. useTransition vs useDeferredValue Aspect useTransition useDeferredValue You control the state update a value passed down Typical use filters, tabs, navigation search input to results Pending indicator isPending compare value vs deferred Needs memoized child not always yes

Verification

Record the interaction in the Performance panel with CPU throttling: the first paint after input should occur quickly, followed by background rendering in smaller chunks rather than one long task. The Interactions track should show a short duration for the interaction. In the field, INP for the target should drop.

Worked Example: A Product Search Page

A retailer's search page filtered 3,000 products client-side as users typed. Each keystroke rendered the input and the full result grid synchronously: 220ms per keystroke on a mid-range phone, with visibly lagging input and INP p75 of 310ms. The team passed a deferred query to a memoized results component, dimmed results while stale, and virtualized the grid. Keystrokes painted in under 30ms; results updated in the background, skipping intermediate queries when users typed quickly. INP p75 fell to 110ms, and search abandonment dropped.

How Concurrent Rendering Yields

During a transition, React renders in small units of work and checks whether it should yield to the browser after each. If an urgent update arrives (another keystroke), React pauses or discards the in-progress transition render and handles the urgent update first. Because the main thread is released between units, the browser can paint and respond to input. This is why transitions improve INP: the next paint after an interaction is not blocked by the entire non-urgent render. It also means non-urgent renders may never complete if urgent updates keep arriving — which is fine for intermediate search queries.

INP p75 for search typing Bar chart of INP for typing in a search box with synchronous rendering, a deferred value, and a deferred value plus virtualization. INP p75 for search typing Synchronous rendering 310ms useDeferredValue + memo 150ms + virtualized grid 110ms good

Choosing Which Updates Are Urgent

The decision is a product question as much as a technical one: what must the user see immediately to know their action registered? Usually that is the direct feedback on the control they touched — the character in the input, the pressed state of the button, the checked checkbox, the highlighted tab. Everything derived from it — results, totals, charts, the content of the selected tab — can follow a moment later, as long as there is a pending indicator. Write this down for each key interaction, and structure state so that the urgent part lives in its own state variable, updated outside the transition.

Common Mistakes

  • Wrapping the input value itself in a transition. The input lags; keep it urgent.
  • Deferred value without memoization. The child re-renders with the urgent value anyway.
  • No pending indicator. Users think the click did nothing.
  • Expecting transitions to speed up blocking code. Synchronous loops inside render still block.

Edge Cases

Controlled inputs. Input state must update synchronously; transitions are for derived updates.

Suspense and transitions. Transitions keep showing old content instead of fallbacks when new content suspends, avoiding jarring loading states.

Server Actions. In frameworks with server actions, useTransition tracks pending state for the action.

Older React versions. Concurrent features need React 18+ and the concurrent root API.

FAQ

What is the difference between useTransition and useDeferredValue?

useTransition marks a state update as non-urgent where you set it. useDeferredValue creates a lagging copy of a value for components that should update at lower priority.

Do transitions improve INP?

Yes, when the slow part of an interaction is a large re-render. The urgent update paints quickly, and the expensive render no longer blocks the next paint.

Can I use transitions for data fetching?

Yes. With Suspense-enabled data sources or server actions, transitions keep the current UI visible while new data loads.

Why does my input still lag?

The input's own state may be inside the transition, or the expensive child is not memoized and re-renders with the urgent update.

Is useDeferredValue the same as debouncing?

No. Debouncing waits a fixed time before updating; deferred values render as soon as possible at lower priority and skip stale work automatically.

Do transitions work on the server?

They are a client-side scheduling feature. On the server, rendering happens once per request.

How do I show that an update is pending?

Use isPending from useTransition, or compare the current and deferred values, and dim or annotate the content that is still updating.

Can transitions make total rendering slower?

Slightly, due to yielding overhead, but users perceive the app as faster because urgent feedback is immediate.

Do transitions help on fast devices?

Less noticeably, because renders finish quickly anyway. Their value shows on mid-range and low-end phones, where INP problems concentrate.

Can I use transitions with external state stores?

Stores read via useSyncExternalStore update synchronously, so transitions cannot defer them. Keep the derived, expensive state in React state if you want transitions to apply.