How React 18 uses time slicing and priority lanes to keep the UI responsive.
Concurrent rendering is React 18's ability to prepare multiple versions of the UI simultaneously and interrupt long-running renders to handle more urgent updates. Instead of rendering being a single synchronous operation that blocks the main thread, React can now pause render work mid-tree, yield to the browser, handle urgent updates, then resume or discard the paused work.
React's Fiber reconciler represents each component as a small unit of work (a fiber). The render phase processes one fiber at a time, checking after each unit whether higher-priority work has arrived. If so, it can pause and come back later.
React's scheduler breaks render work into small chunks. After each chunk, it yields control back to the browser β allowing input events, animations, and paints to happen. This prevents the UI from freezing during long renders.
React 18 uses a 'lanes' model to classify updates by priority: SyncLane (user input, must be immediate), InputContinuousLane (ongoing gestures), DefaultLane (normal state updates), TransitionLane (non-urgent UI transitions).
If a high-priority update arrives while React is working on a low-priority render, React pauses the low-priority work, processes the high-priority update completely (including commit), then resumes or restarts the low-priority work.
Because the render phase is pure (no side effects), React can safely discard partially-completed renders and restart them without corrupting application state. Side effects only happen in the commit phase, which is never interrupted.
Breaking render work into ~5ms chunks and yielding to the browser between chunks. Keeps the UI responsive during expensive renders.
A bitmask system classifying updates from SyncLane (most urgent) to OffscreenLane (least urgent). Determines scheduling order.
React's internal task scheduler. Manages a priority queue of render tasks and decides what to work on next based on priority and deadlines.
API to mark a state update as a non-urgent transition. Tells React it can interrupt this work for more urgent updates.
Hook that provides startTransition plus an isPending boolean showing whether a transition is in progress.
1import { useState, useTransition } from 'react';23function SearchPage() {4 const [query, setQuery] = useState('');5 const [results, setResults] = useState([]);6 const [isPending, startTransition] = useTransition();78 function handleInput(e) {9 // Urgent: update the input immediately10 setQuery(e.target.value);1112 // Non-urgent: filtering 10,000 results can wait13 startTransition(() => {14 setResults(filterItems(e.target.value));15 });16 }1718 return (19 <>20 <input value={query} onChange={handleInput} />21 {isPending && <Spinner />}22 <ResultsList results={results} />23 </>24 );25}26// Typing feels instant (urgent update)27// Results update when React has time (transition)
Before React 18, any state update that triggered a slow render would freeze the entire UI until rendering completed. Concurrent rendering means that typing in an input, scrolling, and clicking buttons remain responsive even while React is computing a complex update in the background. This is the foundation of Suspense, streaming SSR, and React Server Components.
A product catalog with 50,000 items needs instant search. Using debounce (300ms delay) feels sluggish. Without debounce, every keystroke triggers a heavy filter that blocks the main thread and makes typing lag.
Traditional approach: debounce β wait 300ms β filter β re-render. The user types 'laptop' and sees nothing for 300ms after each keystroke. Or without debounce: type 'l' β 50ms filter blocks thread β 'a' keystroke is delayed β laggy typing experience.
Use `startTransition` to mark the filter as non-urgent. React processes the input update immediately (typing stays responsive), then renders the filtered results when the main thread is free. If the user types another character before the filter completes, React ABANDONS the in-progress render and starts the new filter β no wasted work.
Takeaway: Concurrent rendering replaces debouncing for CPU-heavy UI updates. It provides a better UX (no artificial delay) with better performance (abandoned renders waste no DOM work). Use useTransition for filtering, sorting, and computing derived state.
Clicking a navigation link to a data-heavy dashboard page freezes the UI for 500ms while the new page renders 200+ chart components. The old page disappears, a blank screen shows, then the new page appears β jarring.
Without concurrent rendering, React must complete the entire render synchronously. Clicking 'Dashboard' unmounts the current page and starts rendering 200 charts. The main thread is blocked for 500ms β no animations, no response to user input, just a white flash.
Wrap the navigation in `startTransition(() => navigate('/dashboard'))`. React keeps the old page visible and interactive while rendering the dashboard in the background. If the user clicks 'Back' during this time, React abandons the dashboard render. The transition completes smoothly with no blank screen.
Takeaway: Concurrent rendering enables 'optimistic UI' for navigation β the old page stays visible until the new one is fully ready. This eliminates white-flash navigation transitions and lets users cancel slow navigations by clicking elsewhere. Next.js App Router uses this pattern by default.