useTransition
Marks a state update as non-urgent, allowing the UI to remain responsive during the transition. Returns a pending flag and a function to wrap low-priority updates.
Signature
const [isPending, startTransition] = useTransition()Return Value
A tuple of [isPending, startTransition]. isPending is true while the transition is rendering. startTransition accepts a callback containing state updates to defer.
Examples
import { useState, useTransition } from 'react';
function TabContainer() {
const [tab, setTab] = useState('home');
const [isPending, startTransition] = useTransition();
function selectTab(nextTab: string) {
startTransition(() => {
setTab(nextTab);
});
}
return (
<div>
<nav>
{['home', 'posts', 'settings'].map(t => (
<button
key={t}
onClick={() => selectTab(t)}
style={{ fontWeight: tab === t ? 'bold' : 'normal' }}
>
{t}
</button>
))}
</nav>
{isPending && <div>Loading...</div>}
<TabContent tab={tab} />
</div>
);
}
function TabContent({ tab }: { tab: string }) {
// Simulate expensive render
const items = Array.from({ length: 1000 }, (_, i) => <p key={i}>{tab} item {i}</p>);
return <div>{items}</div>;
}import { useState, useTransition } from 'react';
function FilteredProducts({ products }: { products: { name: string; category: string }[] }) {
const [category, setCategory] = useState('all');
const [isPending, startTransition] = useTransition();
const filtered = category === 'all'
? products
: products.filter(p => p.category === category);
return (
<div>
<select
value={category}
onChange={e => {
startTransition(() => setCategory(e.target.value));
}}
>
<option value="all">All</option>
<option value="electronics">Electronics</option>
<option value="clothing">Clothing</option>
</select>
<div style={{ opacity: isPending ? 0.7 : 1 }}>
{filtered.map(p => <div key={p.name}>{p.name}</div>)}
</div>
</div>
);
}import { useState, useTransition } from 'react';
function SubmitForm() {
const [data, setData] = useState<any>(null);
const [isPending, startTransition] = useTransition();
async function handleSubmit(formData: FormData) {
startTransition(async () => {
const res = await fetch('/api/submit', {
method: 'POST',
body: formData,
});
const json = await res.json();
setData(json);
});
}
return (
<form action={handleSubmit}>
<input name="email" type="email" required />
<button type="submit" disabled={isPending}>
{isPending ? 'Submitting...' : 'Submit'}
</button>
{data && <p>Success: {JSON.stringify(data)}</p>}
</form>
);
}Common Pitfalls
Wrapping synchronous, cheap state updates in startTransition — there's no benefit unless the resulting render is expensive.
Expecting startTransition to work like setTimeout — it doesn't delay, it schedules at a lower priority.
Putting non-state-setting code inside startTransition — only setState calls inside the callback are deferred.
Not using the isPending flag to provide visual feedback, leaving users unsure if their action registered.
Understanding useTransition
useTransition is a hook for marking state updates as non-urgent, enabling React's concurrent features to keep the UI responsive during expensive re-renders. Unlike useDeferredValue which defers a value, useTransition defers the entire state update and provides a pending flag you can use to show loading indicators.
The startTransition function wraps one or more setState calls. React treats these updates as low-priority — if a higher-priority update comes in (like a user keystroke), React can interrupt the transition render and restart it with the newer data. This interruptible rendering is the core of React's concurrent model and is what prevents the UI from freezing during heavy renders.
The isPending boolean is true from the moment you call startTransition until the transition render completes. This gives you a built-in mechanism for loading states without managing a separate loading variable. You can use it to dim the current content, show a spinner, or disable buttons — providing clear feedback that the UI is updating.
In React 19+, startTransition supports async functions, enabling a powerful pattern for form submissions and data mutations. You can await a fetch call inside startTransition, and isPending will remain true until the entire async operation completes and the state updates are rendered. This eliminates the need for manual loading state management in many common patterns. Combined with useActionState and useOptimistic, transitions form the foundation of React 19's approach to data mutations.
Related Hooks
useDeferredValueDefers updating a part of the UI to keep the rest responsive. Returns a value that may lag behind during urgent updates, allowing non-critical UI to render at a lower priority.
useActionStateManages form action state with built-in pending tracking. Combines the action function pattern with automatic state management for form submissions (React 19+).
useOptimisticShows a different state while an async action is underway, providing instant feedback by optimistically updating the UI before the server confirms the change (React 19+).
More Performance Hooks
Explore All React Hooks
Browse our complete reference of 19 React hooks with signatures, examples, pitfalls, and in-depth explanations.