In React, what does calling useTransition() give you, and what does the isPending value it returns tell you?
answer
- zero-argument hook, two-item return
- one boolean and one function
- the callback marks, it does not delay
- flag stays true until commit
basics
~20 suseTransition() returns a two-item array, [isPending, startTransition]. State updates triggered inside the startTransition callback are marked non-urgent so React can let urgent input go first, and isPending is true while such an update is still rendering.
solid answer
~50 s`useTransition()` takes no arguments and returns a pair: `const [isPending, startTransition] = useTransition()`. You call `startTransition(() => { ... })` and any state updates you trigger synchronously inside that callback are marked as Transitions — React treats them as non-urgent, so a later urgent update such as a keystroke or click can be handled first instead of waiting behind them. `isPending` is a boolean the hook keeps in sync for you: it flips to `true` when a transition you started begins and back to `false` once React commits the resulting UI. Its normal use is a lightweight indicator — dimming the stale panel, disabling a submit button, showing a small spinner — while the old UI stays on screen. It is not a substitute for the update itself: the update still happens, just at lower priority.
go deeper
Memorise the shape: const [isPending, startTransition] = useTransition(), no arguments in, a boolean and a function out. Say plainly that updates inside the callback are marked non-urgent and that isPending is true while they render.
Explain that the callback runs synchronously and only tags updates triggered during it, so an update scheduled in a timer or after an await escapes the marking. Be ready to contrast a transition with debouncing.
Show where the pending flag earns its place in a real UI: keep the previous content visible and dim it rather than swapping in a placeholder, and know that reading isPending re-renders the component that reads it.
Frame the choice of who owns the flag: a transition started deep in a leaf gives that leaf a private isPending, while a shared indicator needs the transition started where the indicator lives. Decide that placement before the pattern spreads through a codebase.
## The signature `useTransition` is a built-in React hook that takes no arguments and returns an array of exactly two things: ```js import { useTransition } from 'react'; function TabBar() { const [isPending, startTransition] = useTransition(); // isPending: boolean // startTransition: (scope: () => void) => void } ``` Like every hook, it must be called at the top level of a component or of another hook. The array is destructured by position, so the names are yours to choose, but `[isPending, startTransition]` is the universal convention. ## What startTransition actually marks `startTransition` takes a callback, usually called the *scope* function, and calls it immediately and synchronously. It does not delay anything, it does not debounce, and it does not make your code asynchronous. What it does is tag every state update triggered while that callback runs as a **Transition** — React's label for "this update is not urgent". ```js function handleSelect(nextTab) { startTransition(() => { setTab(nextTab); // marked as a Transition }); } ``` A normal update, such as one made straight from a click or keypress handler, is urgent: React wants it on screen as fast as possible. A Transition tells React the opposite — the resulting render may take a while, and if something urgent arrives in the meantime, that urgent work should be served first. The user keeps interacting with the currently displayed UI while the transition's render proceeds. Because the marking depends on the update being triggered *during* the synchronous scope, an update scheduled later escapes it. This does not mark anything: ```js startTransition(() => { setTimeout(() => setTab(nextTab), 0); // urgent, not a Transition }); ``` ## What isPending is for `isPending` is a boolean piece of state the hook maintains. It becomes `true` as soon as a transition started from *this* hook instance is in flight, and returns to `false` when React commits the resulting UI. Reading it re-renders your component when it flips, which is exactly what you want for feedback: ```js <button onClick={() => startTransition(() => setTab('posts'))} disabled={isPending}> Posts </button> {isPending && <Spinner />} ``` The important property is that the *old* UI is still on screen while `isPending` is `true`. That is the difference from a plain loading flag around a blank area: nothing was torn down, so you can style the stale content as inactive rather than replacing it with a placeholder. If you do not need that flag, React also exports a standalone `startTransition` function that marks updates the same way but gives you no pending state. ## React 19: async scopes In React 19 the scope function may be `async`. React keeps `isPending` `true` from the start of the async function until the final state update it makes has been committed, which is what lets a submit handler show pending state for the whole request rather than only for the render: ```js startTransition(async () => { await saveName(name); setSaved(true); }); ``` Functions used this way are what React 19 calls **Actions**, and the form-oriented hooks build on the same idea. ## Common mistakes Candidates often describe `startTransition` as "making the update async" or "debouncing it". Neither is true: no time is inserted, and the update is never dropped for being late — it is only allowed to yield to more urgent work. Another frequent error is expecting `useTransition` to take the setter or the value as an argument; it takes nothing. Finally, `isPending` belongs to the hook call that started the transition, so a component that only imports the standalone `startTransition` has no pending flag at all and must get one another way. ## Where it fits Reach for `useTransition` when *you* trigger the expensive state update and want a pending affordance: switching a heavy tab, applying a filter, navigating within a client-rendered view. When the expensive render is driven by a value you receive rather than one you set, the companion API `useDeferredValue` is the better shape.
- Does wrapping a call in startTransition delay it or debounce it?No. `startTransition` invokes its callback immediately and synchronously; nothing is delayed and nothing is dropped. The only change is priority: the state updates triggered inside are labelled non-urgent, so React may pause their rendering to handle an urgent update such as a keystroke first, then finish. Debouncing skips work; a transition still does all of it.
- If you schedule the state update inside a setTimeout within the startTransition callback, is it still a Transition?No. Only updates triggered synchronously while the scope function runs are marked. By the time the timeout fires, the scope has returned and React treats the update as urgent. If you must update after an await or a timer, wrap that update in its own `startTransition` call at the point where it happens.
- Can you call useTransition twice in one component, and what does that give you?Yes — hooks are per call site, so each `useTransition()` keeps its own `isPending`. That is useful when two independent slow updates need separate indicators: one flag can be true for the filter panel while the other is false for the sort control. A single shared hook would light both indicators for either action.
saying these in an interview costs you the question
- Says startTransition debounces or delays the update
- Thinks the update inside a transition may be discarded
- Claims useTransition takes the state setter as an argument
- Expects an isPending flag from the standalone startTransition
- Describes isPending as a data-loading flag from the network