"Concurrent rendering keeps your UI responsive" is one of those React lines that means nothing until you feel a laggy input turn smooth. So I built a lab that filters a genuinely heavy list three ways — and a live main-thread heartbeat that freezes to prove when the thread is blocked.
▶ Live demo: https://concurrent-lab.vercel.app/
Source (React 19): https://github.com/dev48v/concurrent-lab
Same filter, three schedulings
The list has ~1,200 deliberately-costly rows, so rendering it on every keystroke is real work.
blocking (useState) — the filter runs synchronously; rendering the heavy rows blocks the main thread, so the input stutters and the heartbeat dot stops moving (the demo shows the worst frame gap — I've seen 270ms+).
useDeferredValue — the input binds to the live value; the list reads a deferred copy:
const [text, setText] = useState("");
const deferred = useDeferredValue(text); // lags behind, low priority
const list = useMemo(() => filter(deferred), [deferred]);
// input value={text} stays instant; while the list catches up, show it dimmed
useTransition — wrap the state update that triggers the heavy work:
const [isPending, startTransition] = useTransition();
const onChange = (v) => {
setText(v); // urgent: input updates now
startTransition(() => setQuery(v)); // non-urgent: list re-renders at low priority
};
With either, the input stays perfectly responsive and the heartbeat keeps ticking, while the list updates a beat later (with an isPending/"updating" badge and a dimmed old list).
When to use which
-
useDeferredValue— you already have the value (a prop, a piece of state) and want a lower-priority copy for the expensive part. No control over the setter needed. -
useTransition— you own the state update that causes the heavy work, and you want anisPendingflag to show a spinner. Also great for tab/route switches.
Both are React saying: this update is low-priority — keep the UI interactive and finish it when you can. And unlike debouncing, React can interrupt the in-progress render if the user types again, instead of just delaying it.
If this made concurrent React click, a star helps others find it: https://github.com/dev48v/concurrent-lab
Top comments (0)