← All posts

React 19: concurrent rendering, Actions, and Suspense in production

Deep dive into React 19 concurrent rendering: urgent vs transition updates, useTransition, useOptimistic, and Suspense boundaries without blocking the UI.

Contents

In brief

A Dev.to deep dive into React 19 concurrent rendering: splitting urgent updates (typing, clicks) from background work (filtering, mutations) so the UI does not freeze for hundreds of milliseconds. Core primitives — Actions, useTransition, useOptimistic, and coordinated Suspense.

What happened

The author starts from a common mistake: every network call wrapped in blocking loading, UI frozen on mutations, search stuttering on large lists. React 19 fixes this with priorities: the framework can interrupt, defer, and resume render work.

The architecture introduces two update lanes:

  • Urgent — keystrokes, clicks; commit immediately.
  • Transition — heavy filtering, computation; yield to urgent work.

useTransition returns [isPending, startTransition]: wrapping a state update marks it non-urgent; isPending drives local indicators without blocking the page. If the user types faster than filtering finishes, React drops the stale transition and starts fresh — no manual debounce or request cancellation.

useOptimistic formalizes optimistic UI: show intended results before the server responds; React rolls back on error. For chat and forms, that removes hand-written rollback sync.

Suspense in React 19 coordinates multiple async boundaries: progressive fallbacks instead of one page spinner, one final commit without flicker.

Why it matters

Users feel responsiveness in milliseconds. Search over 5–10k rows that lags every keystroke breaks trust. The concurrent model moves priority complexity into the framework — fewer isLoading booleans and setState chains in handlers.

Migration is incremental: profile with React DevTools, wrap expensive updates in startTransition, add Suspense around independent async sections. The author cites 40–60% less blocking time on heavy UIs with modest memory overhead for transition tracking.

In practice

  1. Search/filters — update query synchronously, filter inside startTransition; use isPending for dim/spinner.
  2. Forms and chatuseOptimistic + server action in a transition; automatic rollback on error.
  3. Data — nested Suspense per independent section, not one boundary for the whole layout.
  4. Threshold — transitions pay off for work >100 ms; instant updates add overhead only.
Pattern Without concurrent With React 19
5k-item search Input blocked ~300 ms Instant input, filter in background
Form submit Full-page freeze Optimistic + transition
Multiple fetches One spinner Progressive fallbacks

Takeaway

React 19 concurrent rendering is not “another hook” — it changes the model: user interaction beats background work. Start with search and heavy lists; wins show up immediately without rewriting the whole app.