Concurrent features allow React to interrupt, pause, and resume rendering work to keep the UI responsive. Before React 18, rendering was synchronous — once it started, it couldn't be stopped. Concurrent mode changes this fundamentally.
The mental model: Think of rendering like a chef cooking multiple dishes. Before, the chef had to finish one dish completely before starting another. Now, the chef can pause a complex dish to quickly serve a simple one, then come back. Urgent tasks (user clicks, typing) always get priority.
1. useTransition — mark updates as non-urgent: Tells React that a state update can be interrupted to handle more urgent updates:
1import { useState, useTransition } from "react";23function TabContainer() {4 const [isPending, startTransition] = useTransition();5 const [tab, setTab] = useState("home");67 const selectTab = (nextTab: string) => {8 startTransition(() => {9 setTab(nextTab); // Can be interrupted by clicks/typing10 });11 };1213 return (14 <div>15 <TabButton onClick={() => selectTab("home")} disabled={isPending}>16 Home17 </TabButton>18 <TabButton onClick={() => selectTab("analytics")} disabled={isPending}>19 Analytics20 </TabButton>21 <div style={{ opacity: isPending ? 0.6 : 1 }}>22 {tab === "home" ? <Home /> : <Analytics />}23 </div>24 </div>25 );26}2728function TabButton({ onClick, disabled, children }) {29 return (30 <button onClick={onClick} disabled={disabled}>31 {children}32 </button>33 );34}
2. useDeferredValue — defer expensive re-renders: Creates a deferred version of a value that React can update later (lower priority):
1import { useState, useDeferredValue, useMemo } from "react";23function SearchResults({ query }: { query: string }) {4 const deferredQuery = useDeferredValue(query);5 const isStale = query !== deferredQuery;67 // Expensive filtering with deferred value8 const results = useMemo(() => {9 return filterLargeList(allItems, deferredQuery);10 }, [deferredQuery]);1112 return (13 <div style={{ opacity: isStale ? 0.5 : 1 }}>14 <p>{results.length} results for "{deferredQuery}"</p>15 <ul>16 {results.map(item => (17 <li key={item.id}>{item.name}</li>18 ))}19 </ul>20 </div>21 );22}2324function App() {25 const [query, setQuery] = useState("");26 return (27 <>28 <input value={query} onChange={e => setQuery(e.target.value)} />29 <SearchResults query={query} />30 </>31 );32}
3. startTransition — for imperative code: Use outside of event handlers for non-urgent updates:
1import { startTransition } from "react";23// In a non-event-handler context4startTransition(() => {5 setFilter(newFilter);6});
How React prioritizes updates:
When to use concurrent features:
Performance note: Concurrent features don't make rendering faster — they make it non-blocking. The total work is the same, but the UI stays responsive during that work.