useTransition lets you mark a state update as non-urgent. React renders that update in the background and drops or restarts it if something more urgent happens, like a click or a keystroke, so the page stays responsive while a slow part of the screen catches up.
Click posts and then, before the list appears, click contact. Contact shows at once: React threw away the half-finished posts render. Now change startTransition(() => { setTab(next); }) to a plain setTab(next); and try the same thing: the page freezes for about 300 ms after clicking posts, and your click on contact waits until the posts are done.
The syntax
const [isPending, startTransition] = useTransition();
startTransition(() => {
setTab(next);
});
startTransitiontakes a function and runs it right away. Any state updates inside it are marked as transitions.isPendingistruefrom the moment you start the transition until React has finished rendering it and committed the result.
useTransition takes no arguments. The function you pass runs synchronously; the transition is about how React renders the resulting update, not about when your function runs.
What "non-urgent" means
React splits updates into two kinds. Urgent updates, such as typing, clicking and pressing, need to reflect immediately or the page feels broken. Transition updates, such as switching to a new view or showing filtered results, can take a moment.
React renders a transition in small slices and checks between components whether something urgent came in. If it did, React pauses or abandons the transition, handles the urgent update, and then starts the transition again with the latest state. While a transition renders, the previous screen stays up; React does not show a half-built result. That is why the example keeps showing the about text, with the Loading hint beside the buttons, until the posts are ready.
Interruption happens between components. A single component that blocks for 300 ms cannot be cut in half, which is why the slow work above is spread over 300 small SlowPost components.
Showing a pending hint
isPending is the place for a loading indicator that does not replace the current content: a spinner next to the button, dimmed text, a "Saving..." label. In the first example it shows Loading... beside the tabs. You can also use it to style the content:
<div style={{ opacity: isPending ? 0.6 : 1 }}>{content}</div>
Filtering a big list while typing
You cannot put an input's own value in a transition. A controlled <input> must receive its new value in the same urgent update, or characters get dropped. Keep two pieces of state instead: the text of the input (urgent) and the query the list uses (a transition).
Type 123 quickly. The input never lags. The Console still logs one rendering results line per keystroke, because Results logs as soon as React starts rendering it, but the lists for 1 and 12 never reach the screen: each newer keystroke interrupted the render in progress, and React started again with the latest query. Results is wrapped in memo on purpose: without it, the urgent render for each keystroke would also render the slow list with the old query, and you would lose the benefit.
startTransition without the hook
react also exports startTransition as a plain function. It marks updates as transitions just like the hook does, but you get no isPending. Use it where you cannot call a hook, for example in a data library or a router outside any component.
import { startTransition } from 'react';
function navigate(url) {
startTransition(() => {
setCurrentUrl(url);
});
}
Async transitions in React 19
Since React 19 the function you pass can be async. isPending then stays true for the whole time the function runs, including the waiting, which makes it a simple way to show a pending state during a request. The fake saveName below stands in for a real request.
Note the second startTransition around setSaved. React loses track of the transition across an await, so a state update after it has to be wrapped again to count as part of the transition. React calls a function passed this way an action; useActionState and form actions build on the same mechanism.
A transition is not a debounce
A debounce waits a fixed amount of time after the last keystroke before doing anything. A transition does not wait. It starts rendering immediately, and if a newer update arrives it drops the old work. On a fast device the result appears with no delay at all; on a slow one, React does as much as it can between keystrokes. What a transition cannot do is reduce the number of network requests: if each keystroke starts a fetch, debounce the fetch.
Transitions and Suspense
Transitions also change what a Suspense boundary does. Normally, when a component inside a boundary suspends (a lazy component loading, or data read with use), React replaces the content with the fallback. If the update that caused the suspension was a transition, React keeps the content that is already on screen instead, and isPending stays true until the new content is ready. This is how routers avoid flashing a page-sized spinner on every navigation: they wrap the navigation in a transition, and the old page stays visible until the next one can render. Content that has never been shown still uses the fallback, so a newly revealed boundary behaves as usual.
Things that do not work
- Updates inside a timeout.
startTransition(() => setTimeout(() => setTab(next), 100))does not mark the update, because it runs afterstartTransitionhas returned. PutstartTransitioninside the timeout instead. - A single slow component. React can only interrupt between components, so one component that blocks for a long time still blocks.
- Controlling an input. As above, the input's own state stays urgent.
For the case where you do not own the state setter and only receive a value, such as a prop, useDeferredValue gives you the same behaviour.
Frequently Asked Questions
What does useTransition do?
It gives you startTransition, which marks the state updates inside it as non-urgent, and isPending, which is true while that update is still rendering. React can interrupt a non-urgent render to handle clicks and typing first.
What is the difference between useTransition and startTransition?
Both mark updates as transitions. The useTransition hook also gives you isPending for a loading hint. The standalone startTransition imported from react works outside components but has no pending flag.
Can I use useTransition for a text input?
Not for the input's own value. A controlled input must update synchronously or typing breaks. Keep the input's state urgent and put the expensive update that depends on it in a transition, or use useDeferredValue.
Is useTransition the same as debouncing?
No. A debounce waits a fixed time before doing anything. A transition starts rendering right away and is interrupted if a newer update arrives, so on a fast device the result appears with no delay.
Can startTransition take an async function?
Yes, since React 19. isPending stays true until the async function finishes. State updates after an await must be wrapped in another startTransition call to be marked as a transition.