Skip to main content

What is "startTransition()"?

What startTransition() is

startTransition(callback) tells React that the state updates inside callback can be deferred (they have low priority).

In simpler terms:

It's a way to tell React: "Do this update, but don't rush, render what matters for UX first (for example, text input)."


Example without startTransition

javascript
function Search() { const [query, setQuery] = useState(''); const [results, setResults] = useState([]); function handleChange(e) { const value = e.target.value; setQuery(value); // Heavy filtering (blocks input) 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: on fast typing, React has to re-render a huge list for every character. The UI "freezes" - the cursor lags behind.


The fix with startTransition

javascript
import { startTransition, useState } from 'react'; function Search() { const [query, setQuery] = useState(''); const [results, setResults] = useState([]); function handleChange(e) { const value = e.target.value; setQuery(value); // urgent update (text input) // deferred (low-priority) update startTransition(() => { 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> </> ); }

What happens:

  1. React sees that state updates are inside startTransition();
  2. It queues them with low priority;
  3. It first renders the fast, important changes (setQuery);
  4. Then, when "free", it does the "heavy" ones (setResults);
  5. As a result, the interface stays responsive.

Visually

Without startTransitionWith startTransition
Input is blockedInput is smooth
Rendering a huge list stalls thingsReact defers rendering the list
The UI feels "heavy"The UI is responsive and alive

1. How "priority" works in React 18

React now distinguishes two types of updates:

Type of updateExamplePriority
UrgentText input, clicks, scrollingRun immediately
Non-urgent (Transition)Updating a list, filtering, loading dataRun when React is "free"

startTransition() marks updates as "non-urgent". React can pause, cancel, or restart them if new urgent updates arrive in the meantime.


2. With useTransition() - managing transition state

React also provides the useTransition() hook - a convenient wrapper around startTransition() with a loading indicator:

javascript
import { useTransition, useState } 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); startTransition(() => { const filtered = hugeList.filter(item => item.includes(value)); setResults(filtered); }); } return ( <> <input value={query} onChange={handleChange} /> {isPending && <p>Updating results...</p>} <ul>{results.map(r => <li key={r}>{r}</li>)}</ul> </> ); }

useTransition() returns:

  • isPending - true while the transition is running (handy for showing a loader);
  • startTransition() - a function for running low-priority updates.

3. Asynchronous scenarios: API requests

javascript
import { useState, startTransition, 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(''); function handleChange(e) { const value = e.target.value; setQuery(value); // update the "visible" query with a delay startTransition(() => setDisplayQuery(value)); } return ( <> <input value={query} onChange={handleChange} placeholder="Search..." /> <Suspense fallback={<p>Loading...</p>}> <SearchResults query={displayQuery} /> </Suspense> </> ); }

On fast typing:

  • React updates the field instantly (query);
  • it defers changing displayQuery and making a new request;
  • the UI stays responsive even with large amounts of data.

4. Difference from useDeferredValue()

Hook / APIWhat it doesExample
startTransition(fn)defers the execution of state updatesstartTransition(() => setList(...))
useDeferredValue(value)defers the use of a valueconst deferred = useDeferredValue(value)

Both solve the same problem - keeping the interface smooth - but they apply at different levels:

  • startTransition() - when you set state;
  • useDeferredValue() - when you use state in a component.

5. Where startTransition() is useful

ScenarioBenefit
Search across a large arrayfast typing, filtering deferred
Switching tabsthe old tab stays until the new one is ready
Rendering large tables / listsReact doesn't freeze on updates
Suspense + APIsmooth asynchronous transitions
Updating charts / filtersno lag or blocking the UI

Summary

What it doesWhy it matters
Marks updates as "low priority"React can defer them
Keeps the UI responsivefast actions (typing, clicking) run right away
Enables smooth transitionsno "freezes" and "flicker"
Works with useTransition() and Suspensefull control over async UX
Foundation of Concurrent RenderingReact itself decides what to update first

In short:

startTransition() is a way to tell React: "This update isn't urgent - do it whenever it's convenient."

It makes the interface instantly responsive, especially with heavy renders, filtering, and asynchronous loading.

Short Answer

Interview ready
Premium

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