Skip to main content

React Concurrent Rendering: Complete Guide 2026

React concurrent rendering is the ability for React to interrupt a long-running render and switch to higher-priority work, then resume the original render later. This doesn't block the main thread; instead, React's Fiber scheduler breaks rendering into small chunks, yields to the browser's event loop, and prioritizes user interactions over background updates. Introduced officially in React 18 and refined in React 19, concurrent rendering is the foundation for responsive, performant modern applications.

Before concurrent rendering, React would start rendering a component tree and not stop until the entire tree was committed to the DOM. If that tree was large or the render function was slow, the main thread would be blocked and the browser couldn't respond to user input, scroll events, or lower-priority tasks for that entire duration. Concurrent rendering solves this by building React on top of the Fiber architecture—a system that represents each component instance and hook as a unit of work that can be paused and resumed.

How React's Fiber Architecture Enables Concurrency

React's render phase (preparing the update) is now completely separate from the commit phase (applying the update to the DOM). The render phase can be interrupted and restarted multiple times, while the commit phase remains synchronous and atomic. Under the hood, React maintains a work loop that processes Fibers in priority order, checking between each unit of work whether a higher-priority update has arrived. If one has, React abandons the current work, saves its progress, and switches to the urgent task.

Here's a conceptual example of how the scheduler prioritizes work:

// React's internal priority queue (simplified)
// React processes updates in priority order:
// 1. User input (clicks, keyboard) — IMMEDIATE
// 2. Layout effects, state updates from handlers — HIGH
// 3. Default state updates from events — NORMAL
// 4. Background updates (debounced, transitions) — LOW
// 5. Passive effects (data fetching side effects) — IDLE

// Your code doesn't directly use this, but useTransition and
// useDeferredValue let you mark updates as LOW priority.

The Fiber architecture assigns each piece of work a lane (or priority level). When React's scheduler sees a new lane with higher priority than the current work, it pauses, saves the in-progress render, and starts the high-priority update. If the high-priority update finishes before significant time has passed, React may continue the low-priority work on the same render pass. If not, the low-priority work is abandoned and re-rendered from scratch when it becomes urgent again.

Automatic and Explicit Priority Marking

By default, React assigns medium priority to state updates triggered by event handlers. Updates from useState calls inside a click handler get normal priority. But if you want an update to be genuinely non-urgent—like filtering a large list while the user is still typing—you should explicitly mark it as a transition using useTransition or startTransition.

Transitions tell React: "I'm about to start a long-running render, so if higher-priority work (like another keypress) arrives, pause this and handle that first." This prevents the UI from feeling frozen during expensive renders.

Concurrent rendering is not automatic optimization—it's a mental model and a set of APIs you opt into. You must decide which updates are urgent and which can be interrupted. The browser's native scheduler (via requestIdleCallback and MessageChannel) handles the actual interleaving with React's event loop, but React's Fiber scheduler is what decides which work to run next.

Real-World Impact: Before and After

Without concurrent rendering (React 17 and earlier), a large list filter would block the main thread for 200–500 ms while React re-rendered the entire list. The user would see the UI freeze, keystrokes would queue up, and scrolling would stutter.

With concurrent rendering (React 18+) and proper use of useTransition:

// React 19 - Concurrent Rendering Example
import { useState, useTransition } from 'react';

export function ListFilter() {
const [query, setQuery] = useState('');
const [isPending, startTransition] = useTransition();
const [filteredList, setFilteredList] = useState([]);

// Simulating expensive list filtering
const handleFilterChange = (e) => {
const newQuery = e.target.value;
setQuery(newQuery); // Update input immediately (urgent)

// Start an expensive filter as a transition (low priority)
startTransition(() => {
const results = expensiveFilter(newQuery);
setFilteredList(results);
});
};

return (
<div>
<input
value={query}
onChange={handleFilterChange}
placeholder="Filter list (concurrent render demo)"
/>
{isPending && <p>Filtering...</p>}
<ul>
{filteredList.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}

// Expensive helper function
function expensiveFilter(query) {
const items = generateMillionItems();
return items.filter(item =>
item.name.toLowerCase().includes(query.toLowerCase())
);
}

function generateMillionItems() {
return Array.from({ length: 1000000 }, (_, i) => ({
id: i,
name: `Item ${i}`,
}));
}

In this example, the input field updates immediately (the setQuery call has high priority), while the expensive filter runs as a transition. React can interrupt the filtering if the user presses a key, ensuring the input stays responsive.

Key Takeaways

  • Concurrent rendering breaks React's work into small chunks and pauses to allow the browser to handle higher-priority tasks like user input.
  • The Fiber architecture represents each component instance as a unit of work that can be paused, saved, and resumed.
  • React's internal scheduler assigns priority lanes to updates—user interactions are immediate, and transitions are low-priority.
  • You don't get concurrent rendering benefits automatically; you must opt in by using useTransition and useDeferredValue to mark non-urgent updates.
  • Concurrent rendering keeps UI responsive during expensive renders like large list filtering or complex state updates.

Frequently Asked Questions

Does concurrent rendering change how I write component logic?

No. Your component code remains the same. Concurrent rendering is transparent—React handles the scheduling internally. You only need to mark specific state updates as transitions using useTransition if they're long-running.

Is concurrent rendering always enabled in React 19?

Concurrent features are enabled by default in React 19, but you don't get the benefits unless you opt into transitions with useTransition or useDeferredValue. Without those APIs, updates behave much like React 17.

Can concurrent rendering cause state to be inconsistent?

No. React guarantees that only one render pass commits to the DOM at a time. Abandoned renders don't leak state or cause visible inconsistencies. All committed updates are atomic.

What happens if an update is abandoned mid-render?

React discards the incomplete render work and starts over with the new higher-priority update. The abandoned work's state changes are never committed, so the old UI remains visible until a new render completes.

Further Reading