Concurrent React and Transitions
React 18 introduced a fundamental change in the rendering model: Concurrent Rendering. Instead of blocking the browser thread during heavy renders, React can now pause, resume, and discard renders in progress to keep the UI responsive.
1. What is Concurrent Rendering?
Historically, rendering in React was synchronous and blocking. Once an update started rendering, nothing could interrupt it until it finished. In Concurrent React, rendering is interruptible.
If a user types into an input box while a large list is rendering, React can pause the list rendering, handle the input change instantly, and then resume the list rendering in the background.
2. Using useTransition for Non-Urgent States
State updates in React are categorized into two types:
- Urgent Updates: Reflect direct interaction (like typing, clicking, or dragging).
- Transition Updates: Transition the UI from one view to another (like loading search results or switching tabs).
The useTransition hook allows you to mark specific state updates as non-urgent transitions, ensuring they do not block urgent user interactions:
import { useState, useTransition } from "react";
function SearchPanel() {
const [query, setQuery] = useState("");
const [list, setList] = useState([]);
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
// 1. Urgent update: update input immediately
setQuery(e.target.value);
// 2. Transition update: mark heavy filter calculation as non-urgent
startTransition(() => {
const filtered = generateLargeList(e.target.value);
setList(filtered);
});
};
return (
<div>
<input type="text" value={query} onChange={handleChange} />
{isPending && <p>Loading results...</p>}
<ul>
{list.map((item, idx) => (
<li key={idx}>{item}</li>
))}
</ul>
</div>
);
}3. Postponing Updates with useDeferredValue
The useDeferredValue hook accepts a value and returns a deferred version of that value. It behaves similarly to debouncing, but it is integrated directly into React rendering loop:
import { useState, useDeferredValue } from "react";
import HeavyList from "./HeavyList";
function Dashboard() {
const [query, setQuery] = useState("");
// Defer the query updates for heavy child components
const deferredQuery = useDeferredValue(query);
return (
<div>
<input value={query} onChange={e => setQuery(e.target.value)} />
{/* HeavyList updates in background using deferredQuery */}
<HeavyList query={deferredQuery} />
</div>
);
}