MENU
useTransition
useTransition() returns [isPending, startTransition]. isPending is a boolean that's true while a Transition is in flight. startTransition is a function that takes a callback – any state updates you make synchronously inside that callback are marked as a Transition: a low-priority update that React is allowed to interrupt, whose expensive re-render can happen in the background while the rest of the UI — and any more urgent update, like the next keystroke – keeps responding immediately.
The callback passed to startTransition runs immediately and synchronously; only the set calls made directly inside it (not, say, ones made later inside a setTimeout) get marked as the Transition. Because of this, Transition updates aren't suitable for driving something like a text input's value – that still needs an ordinary, immediate state update.
RESETRUNFULL
<!DOCTYPE html><html><head>
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head><body>
<div></div>
<script type="text/babel">
function SlowItem({ text }) {
const start = performance.now();
while (performance.now() - start < 1) {} // simulate an expensive item to render
return <li>{text}</li>;
}
function SlowList() {
const items = [];
for (let i = 0; i < 300; i++) {
items.push(<SlowItem key={i} text={'Item #' + i} />);
}
return <ul>{items}</ul>;
}
function App() {
const [tab, setTab] = React.useState('about');
const [isPending, startTransition] = React.useTransition();
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
return (
<React.Fragment>
<button onClick={() => selectTab('about')}>About</button>
<button onClick={() => selectTab('slow')}>Slow tab</button>
<p>{isPending ? 'Loading...' : 'Ready'}</p>
{tab === 'about' ? <p>Welcome to the About tab.</p> : <SlowList />}
</React.Fragment>
);}
const root = ReactDOM.createRoot(document.querySelector("div"));
root.render(<App/>);
</script>
</body></html>Since React 19, the function passed to startTransition may also be async – work it awaits is included in the Transition (state updates scheduled after that first await currently still need wrapping in their own nested startTransition call). This is what underpins React's newer Actions pattern, covered separately elsewhere in this tutorial. React also exports a standalone startTransition function for marking updates from places that aren't components or Hooks at all, such as a plain data module – the next page compares it directly against this Hook.