Skip to main content

What does useTransition() do?

What useTransition() does

useTransition() lets you mark part of a state update as a "transition" - that is, a lower-priority update that React can defer in favor of more important ones (such as text input, clicks, animations).

In simpler terms:

This is a way to tell React: "This update is not urgent - you can do it a bit later, once you're done with the fast stuff."


Example without useTransition

javascript
function Search() { const [query, setQuery] = useState(''); const [results, setResults] = useState([]); function handleChange(e) { const value = e.target.value; setQuery(value); // Emulate heavy filtering const filtered = hugeList.filter(item => item.includes(value)); setResults(filtered); } return ( <> <input value={query} onChange={handleChange} /> <ul>{results.map(r => <li key={r}>{r}</li>)}</ul> </> ); }

The problem: when typing fast, React re-renders the huge list on every character, which makes the input "lag" - the cursor stutters and updates slow down.


1. The solution with useTransition

javascript
import { useState, useTransition } from "react"; function Search() { const [query, setQuery] = useState(''); const [results, setResults] = useState([]); const [isPending, startTransition] = useTransition(); function handleChange(e) { const value = e.target.value; setQuery(value); // update the input right away (high priority) startTransition(() => { // update the results later (low priority) const filtered = hugeList.filter(item => item.includes(value)); setResults(filtered); }); } return ( <> <input value={query} onChange={handleChange} /> {isPending && <p>Loading...</p>} <ul>{results.map(r => <li key={r}>{r}</li>)}</ul> </> ); }

What React does:

  1. setQuery() runs immediately → the input field stays responsive;
  2. React defers setResults() - this is a "transition update";
  3. While the transition runs → isPending = true (you can show a loader);
  4. When React finishes the transition, isPending resets.

The UI stays smooth, typing is instant, the list updates a bit later, but without lag.


What happens "under the hood"

React now has two update priorities:

Update typeExamplePriority
UrgentText input, clicks, scrollingRuns immediately
TransitionFiltering, loading data, switching tabsCan be paused

When you call startTransition(fn):

  • React puts the updates inside fn into a low-priority queue;
  • it processes urgent updates first (for example, rendering the input);
  • then, when it's "free", it runs the transition;
  • if the user does something during the transition → React can interrupt the transition and start over (without freezing).

2. Asynchronous scenarios: API requests

javascript
import { useState, useTransition, Suspense } from 'react'; import useSWR from 'swr'; const fetcher = url => fetch(url).then(r => r.json()); function SearchResults({ query }) { const { data } = useSWR(`/api/search?q=${query}`, fetcher, { suspense: true }); return <ul>{data.results.map(r => <li key={r}>{r}</li>)}</ul>; } export function Search() { const [query, setQuery] = useState(''); const [displayQuery, setDisplayQuery] = useState(''); const [isPending, startTransition] = useTransition(); function handleChange(e) { const value = e.target.value; setQuery(value); startTransition(() => setDisplayQuery(value)); // asynchronous transition } return ( <> <input value={query} onChange={handleChange} /> {isPending && <p>Loading...</p>} <Suspense fallback={<p>Loading results...</p>}> <SearchResults query={displayQuery} /> </Suspense> </> ); }

What the code does:

  • the user types fast → React updates the input instantly;
  • displayQuery updates "deferred" → SWR triggers a fetch;
  • until the data arrives → React shows the Suspense fallback;
  • everything is smooth, without blocking the interface.

3. The values useTransition() returns

javascript
const [isPending, startTransition] = useTransition();
VariableDescription
isPendingtrue while the transition is running (you can show a loader / disable a button)
startTransition(cb)runs a function as a "deferred" state update

4. The difference from useDeferredValue()

HookWhat it doesWhen to use it
useTransition()defers running a state updatewhen you set state (setState)
useDeferredValue()defers using a valuewhen you pass a value into a heavy component

Example:

javascript
// useTransition startTransition(() => setQuery(value)); // update state deferred // useDeferredValue const deferredQuery = useDeferredValue(query); // use the value deferred

5. Real-world cases

ScenarioHow it helps
Searching a large listinstant typing, filtering deferred
Switching between tabsthe old tab stays until the new one is ready
Updating tables / chartssmooth UI while filtering
Navigating to another page (SPA)React shows the "old" page until the new one loads
Suspense + APIbackground data loading without blocking

6. What happens visually

Without useTransitionWith useTransition
The UI freezes while typingThe UI is responsive
New content replaces the old one instantly (flickers)The old content stays until the new one is ready
The transition feels "harsh"The transition is smooth and natural

SUMMARY

What it doesWhy it matters
Defers "heavy" updatesThe UI stays responsive
Separates prioritiesReact doesn't block input and clicks
Returns isPendingyou can show an indicator
Works with Suspensesmooth async transitions
Simplifies UX with large datafilters, search, sorting

In short:

useTransition() is a way to tell React: "These updates aren't urgent - do them when you're free."

It makes the UI instantly responsive, even when there is asynchronous data loading or a heavy render happening under the hood.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.