Что такое useOptimistic в React 19 и для чего он нужен?
Что это такое
useOptimistic — встроенный хук React 19, предназначенный для реализации паттерна optimistic UI: интерфейс сразу отображает ожидаемый результат действия, не дожидаясь ответа сервера. Как только асинхронная операция завершается, React заменяет временное («оптимистичное») состояние на реальные данные, пришедшие из источника правды (обычно из пропсов, обновлённых после успешного экшена).
Сигнатура
const [optimisticState, addOptimistic] = useOptimistic(
state,
(currentState, optimisticValue) => newState
);
state— базовое, «настоящее» состояние (обычно приходит сверху как проп).updateFn— чистая функция, которая описывает, как наложить оптимистичное значение на текущее состояние.optimisticState— то, что реально рендерится в UI.addOptimistic— функция, которую вызывают синхронно перед стартом асинхронного действия (внутриactionформы или обработчика).
Зачем он нужен
До React 19 optimistic-обновления реализовывали вручную через useState: сохраняли временное значение, вызывали запрос, а в finally/catch откатывали состояние при ошибке. Это работало, но требовало ручной синхронизации и было источником багов при гонках запросов (race conditions), особенно если пользователь успевал отправить несколько действий подряд.
useOptimistic решает эту задачу декларативно и интегрирован с Actions и useTransition: как только транзишн, в котором был вызван addOptimistic, завершается (успешно или с ошибкой), React автоматически возвращает optimisticState к актуальному state. Не нужно вручную откатывать значение — если проп state не обновился (например, запрос упал), UI сам вернётся к предыдущему согласованному виду.
Как это работает под капотом
useOptimistic держит внутренний «слой» поверх переданного state. Пока не идёт активный переход (transition), optimisticState === state. Когда вызывается addOptimistic(value) внутри startTransition (или action-функции формы), React применяет updateFn(state, value) и рендерит результат немедленно, не дожидаясь завершения транзишна. После завершения перехода временный слой сбрасывается, и на экране остаётся то, что реально пришло в state.
Важно: updateFn должна быть чистой и быстрой — она может вызываться несколько раз (в том числе повторно при конкурентном рендере), поэтому побочные эффекты внутри неё недопустимы.
Типичный сценарий использования
Чаще всего хук применяют вместе с Server Actions и useTransition: пользователь отправляет сообщение в чат или комментарий, интерфейс сразу показывает новую запись с пометкой «отправляется», а сервер в фоне сохраняет данные. Если запрос завершится ошибкой, значение просто исчезнет из optimisticState, и можно показать сообщение об ошибке.
Когда не стоит использовать
- Если результат действия сложно предсказать заранее (например, сервер может изменить данные непредсказуемым образом) — оптимистичное значение введёт пользователя в заблуждение.
- Для действий, где важна строгая консистентность (финансовые операции, где нельзя показывать неподтверждённый результат).
- Как замену полноценной обработке ошибок — откат состояния не показывает причину сбоя, её нужно обрабатывать отдельно.
Отличия от соседних инструментов
useState требует ручного отслеживания «временности» значения и отката. useTransition управляет состоянием ожидания (isPending), но не подменяет данные. useOptimistic решает именно задачу подмены отображаемых данных на время перехода и обычно используется вместе с useTransition/Server Actions, а не вместо них.
Что хочет услышать интервьюер
Кандидат объясняет, что optimisticState автоматически откатывается к базовому state после завершения transition, а не требует ручного отката
Понимание, что updateFn должна быть чистой функцией без побочных эффектов
Знание, что addOptimistic нужно вызывать внутри startTransition или action-обработчика, иначе будет ошибка
Умение привести практический пример: отправка формы/сообщения с мгновенным отображением результата
Понимание связи с Actions и useTransition в React 19, а не восприятие хука как изолированной фичи
Пример: Оптимистичное добавление сообщения в чат
function Thread({ messages, sendMessage }) {
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages,
(currentMessages, newMessage) => [
...currentMessages,
{ ...newMessage, sending: true }, // помечаем как ещё не подтверждённое
]
);
async function formAction(formData) {
const text = formData.get('message');
// сразу показываем сообщение в UI, не дожидаясь ответа сервера
addOptimisticMessage({ id: Date.now(), text });
await sendMessage(text); // реальный серверный экшен
}
return (
<form action={formAction}>
{optimisticMessages.map((m) => (
<div key={m.id}>
{m.text}
{m.sending && <small> (отправляется...)</small>}
</div>
))}
<input type="text" name="message" />
<button type="submit">Отправить</button>
</form>
);
}
Пример: useOptimistic вместе с useTransition вне формы
function LikeButton({ liked, onToggleLike }) {
const [isPending, startTransition] = useTransition();
const [optimisticLiked, setOptimisticLiked] = useOptimistic(liked);
function handleClick() {
startTransition(async () => {
// мгновенно переключаем визуальное состояние лайка
setOptimisticLiked(!optimisticLiked);
await onToggleLike(); // после завершения optimisticLiked синхронизируется с liked
});
}
return (
<button onClick={handleClick} disabled={isPending}>
{optimisticLiked ? 'Нравится' : 'Лайкнуть'}
</button>
);
}
Типичные ошибки
Путают useOptimistic с обычным useState и пытаются вручную откатывать значение в catch-блоке
Вызывают addOptimistic вне транзишна (не в action и не в startTransition), из-за чего React выбрасывает ошибку или обновление не работает как ожидается
Пишут updateFn с побочными эффектами (мутации, запросы), забывая, что функция должна быть чистой и может вызываться повторно
Не обрабатывают ситуацию ошибки отдельно, считая, что откат optimisticState сам по себе информирует пользователя о сбое
Путают порядок аргументов updateFn (currentState, optimisticValue) и логику наложения нового значения


