Конкурентний рендеринг (React 18+) дозволяє React переривати рендер і робити спершу термінову роботу. Не всі оновлення однаково важливі: введення в поле має відгукуватися миттєво, а перерахунок великого списку результатів може трохи почекати.
useTransition позначає оновлення стану як нетермінове (transition):
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState('posts');
function selectTab(next) {
startTransition(() => {
setTab(next); // важкий рендер вкладки можна перервати
});
}
{isPending && <Spinner />}
Поки нова вкладка рендериться, інтерфейс лишається чутливим: клік по іншій вкладці перерве поточний рендер і почне новий. Старий вміст показується, доки новий не готовий, - без «мигання» порожнього екрана.
useDeferredValue - те саме, але для значення, яке ви отримуєте, а не встановлюєте:
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
<input value={query} onChange={(e) => setQuery(e.target.value)} />
<SearchResults query={deferredQuery} /> // memo-компонент
Поле оновлюється одразу, а важкий список рендериться з «відкладеним» значенням у фоні. Щоб це працювало, SearchResults має бути обгорнутий у memo.
Чим це відрізняється від debounce: немає фіксованої затримки. На швидкому пристрої результат з'являється майже одразу, на повільному - React просто не блокує введення.
Обмеження: transitions не роблять рендер швидшим - лише не дають йому блокувати важливіше. Якщо компонент рендериться секунду, треба оптимізувати сам рендер (віртуалізація, мемоізація). У React 19 startTransition приймає й async-функції, а useActionState будується на тому ж механізмі.