Concurrent rendering lets React interrupt and prioritize work — Suspense provides declarative loading states, useTransition marks updates as non-urgent, and useDeferredValue defers expensive re-renders.
React can pause mid-render to handle urgent updates (user input), then resume — the UI never shows incomplete state.
Declarative loading boundaries with fallback UI. Works with React.lazy for code splitting and data fetching frameworks for loading states.
Marks state updates as non-urgent — React renders in the background while keeping the current UI responsive. isPending shows transition status.
Returns a value that lags behind during urgent updates — causes a lower-priority re-render that React can interrupt.
User input (highest) > transitions > deferred values (lowest). Urgent work always takes precedence over background rendering.
Concurrent React (enabled by default since React 18) is a set of features that allow React to interrupt rendering work to handle more urgent updates. This keeps the UI responsive even during expensive state transitions.
In synchronous (pre-React 18) rendering, once React starts rendering a component tree, it can't stop until it's done. If rendering takes 300ms, the browser can't respond to user input during that time — the UI feels frozen.
Concurrent rendering makes rendering interruptible. React can pause mid-render to handle urgent work (user typing, clicking) and resume the interrupted render later. The user never sees an incomplete UI — React only commits the final result to the DOM.
Suspense declares a loading boundary: <Suspense fallback={<Spinner />}><DataComponent /></Suspense>. When a child component is "suspended" (waiting for data, lazy-loaded code, etc.), React shows the fallback UI until the child is ready.
Suspense works with:
React.lazy() — code splitting. const Chart = lazy(() => import('./Chart')) loads the component bundle on demand.Suspense boundaries can be nested: an outer boundary shows a page-level skeleton, inner boundaries show section-level loaders.
const [isPending, startTransition] = useTransition() marks a state update as non-urgent. React keeps showing the current UI while rendering the new state in the background. isPending is true during the transition, letting you show a subtle indicator.
startTransition(() => {
setSearchResults(filterLargeList(query));
});The search input stays responsive (urgent update) while the results list updates in the background (non-urgent). Without transitions, the expensive filter blocks the input.
const deferredQuery = useDeferredValue(query) returns a deferred version of the value that lags behind the actual value during urgent updates. Similar to debouncing, but React-aware — it defers automatically based on rendering priority.
Use it when you can't wrap the state update in startTransition (e.g., the value comes from props). The deferred value causes a lower-priority re-render that React can interrupt.
startTransition(callback) is the function form for use outside hooks (in event handlers, library code). Same behavior as useTransition but without isPending.
React's priority system: user input (typing, clicking) > transitions > deferred values. Urgent updates are processed immediately; non-urgent updates are interruptible and may be restarted if new urgent work arrives.
Concurrent rendering makes React interruptible — it can pause expensive renders to handle urgent input. Suspense is declarative loading states for code splitting and data fetching. useTransition marks updates as non-urgent to keep the UI responsive. useDeferredValue defers a value's update until urgent work completes. These features work together to eliminate UI jank in complex applications.
Fun Fact
Concurrent React was in development for over 4 years before shipping in React 18 (2022). The React team originally called it 'Async React' and demonstrated it at JSConf Iceland in 2018, but the complexity of making it backward-compatible while maintaining correctness took much longer than expected.