useTransition marks a state update as non-blocking so a slow re-render happens in the background instead of freezing the page. It returns an isPending flag and a startTransition function you use to wrap the update.
The update still runs, but React can pause it, handle new input, and come back to it. Urgent updates such as typing or clicking are handled first, which keeps the page responsive.
The API
Call useTransition at the top level of a component. It takes no arguments and returns an array of two items: the isPending flag and the startTransition function.
import { useState, useTransition } from "react";
export default function App() {
const [tab, setTab] = useState("about");
const [isPending, startTransition] = useTransition();
function selectTab(next) {
startTransition(() => setTab(next));
}
return (
<>
<button onClick={() => selectTab("posts")}>Posts</button>
<button onClick={() => selectTab("about")}>About</button>
{isPending && <p>Loading posts...</p>}
{tab === "posts" && <PostsTab />}
{tab === "about" && <AboutTab />}
</>
);
}The function passed to startTransition runs immediately, and every state update inside it is marked as a transition. PostsTab can be slow, but the click response and the button rendering are not blocked while it renders.
The callback is called an action. It can be synchronous or asynchronous, and while it runs, React keeps the UI interactive.
What the user sees
Clicking Posts updates the UI immediately. The isPending flag turns true, so the Loading text appears, and the slow PostsTab renders in the background.
If the user clicks About before it finishes, the pending work is interrupted and the new tab wins, so the interface never feels stuck. That interruption is the key difference from a plain synchronous update.
Where transitions help
Transitions are most useful when one update is much slower than the rest of the page. Tab switches, route changes, and filters over large data sets are the classic cases. In each one, the button or link responds instantly while the heavier content catches up.
- Switching tabs with a slow content panel.
- Navigating between routes.
- Filtering or searching a large data set.
Transitions do not make the slow render faster. They move it off the critical path so the user can keep acting while it runs.
Transitions and Suspense
A transition also changes how Suspense behaves. Without one, a component that suspends during navigation hides the already visible content behind a fallback. Inside a transition, React keeps the current content on screen until the new content is ready, then swaps it in.
The visual result is a navigation that never flashes a spinner over the whole page. isPending is how you reflect the transition, such as dimming the header or disabling the button, instead of showing a blocking spinner.
That is why routers built for Suspense wrap navigation updates in transitions by default. The same coordination applies to lazy components that load on demand.
What transitions do not speed up
A transition does not reduce total render time. The slow component still takes as long to render; it just stops blocking other updates. If the same component re-renders slowly on every keystroke, measure it and optimize the component itself, or use useDeferredValue to let it lag behind.
For example, a chart that recomputes on every keystroke still needs its own optimization, since a transition only stops that work from blocking the input.
Rules and limits
- Do not use a transition to update the state of a controlled input.
- Updates after an await are not marked as transitions, so wrap them again.
- Use the standalone startTransition outside components.
- Transition updates cannot be used to keep an input value in sync.
When the goal is to keep an input responsive while a slower part of the UI lags behind it, useDeferredValue is the simpler tool. The first two rules trip people most often: a controlled input must update synchronously, and an async update after await needs its own startTransition call to stay non-blocking. As always, measure before optimizing so a transition targets a real slow update.
Rune AI
Key Insights
- useTransition returns isPending and startTransition.
- startTransition marks updates inside it as non-blocking.
- Transitions are interruptible, so new input cancels them.
- They prevent Suspense fallbacks from hiding visible content.
- Input state cannot be updated inside a transition.
Frequently Asked Questions
Can I use a transition for a controlled input?
How do I start a transition outside a component?
Conclusion
useTransition marks state updates as non-blocking so slow re-renders happen in the background. The isPending flag gives you a pending state, and Suspense integrates with transitions to keep already visible content on screen.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.