use — промис прямо в рендере
Читает промис или контекст прямо во время рендера. При этом нарушает привычное правило хуков: его можно вызывать условно, внутри цикла и даже внутри if. Если передать промис, компонент приостанавливается через Suspense, пока данные не загрузятся.
Пример показывает, как ведёт себя хук: сначала заглушка Suspense, потом загруженные данные. Рядом — настоящий код React 19.
Условное чтение контекста
Обычные хуки нельзя вызывать после раннего return. use — можно. Поэтому им удобно читать контекст точечно:
Промис нельзя создавать внутри клиентского
компонента (use(fetch(...)) в теле) — на
каждый рендер будет новый промис и бесконечный цикл.
Промис должен приходить из фреймворка или библиотеки с
кешированием (в SPA — из внешнего кеша/лоадера). Для
контекста этого ограничения нет.
useOptimistic — показать до ответа сервера
Показываешь изменение в интерфейсе сразу, ещё до ответа сервера. Если сервер отказал, React сам возвращает настоящее состояние. Чаты, лайки, комментарии — везде, где ждать ответ неприятно.
Включи «Сервер падает» и добавь комментарий — увидишь, как оптимистичный элемент появляется мгновенно, а потом откатывается.
1. addOptimistic обязательно внутри
transition (в action, в
startTransition или в
<form action>). Вызовешь в голом
setTimeout или обычном обработчике —
элемент застрянет навсегда.
2. Откат срабатывает только когда меняется настоящее состояние. Если действие тихо провалилось и не тронуло его, оптимистичное значение так и останется на экране. Поэтому возвращай новое состояние даже при ошибке.
useActionState — форма без трёх useState
Управляет жизненным циклом действия: результат, ошибка, pending — в одном хуке. Заменяет привычную связку isLoading + error + data.
Пароль 1234 — успех, любой другой — ошибка. Смотри, как isPending переключается сам.
useActionState пришёл на замену
экспериментальному useFormState из
canary-сборок react-dom. API почти тот же,
но добавлен третий возврат — isPending. И
импорт теперь из 'react', а не
'react-dom'.
useFormStatus — кнопка знает сама
Даёт вложенному компоненту статус ближайшей родительской <form> без прокидывания пропсов и контекста. Классика — переиспользуемая кнопка Submit, которая сама гаснет на время отправки.
Кнопка внутри формы видит pending. Кнопка «снаружи» — всегда false. Это и есть главная ловушка, показанная вживую.
useFormStatus привязан к
ближайшей родительской <form>.
Поставишь компонент вне формы —
pending навсегда останется
false. Кнопка должна рендериться внутри
формы, а не рядом с ней.
Шпаргалка
Что откуда импортируется и зачем нужно. Импорт путает чаще всего.
| Хук | Импорт | Зачем |
|---|---|---|
use |
react | Читать промис/контекст в рендере. Можно условно. |
useOptimistic |
react | Мгновенный UI + автооткат при провале. |
useActionState |
react | state + isPending для действия/формы. Замена useFormState. |
useFormStatus |
react-dom | Статус родительской формы без пропсов. |
Практика
Четыре задания — по одному на каждый хук. Всё на чистом клиентском React. «Запрос на сервер» везде подделываем через setTimeout, настоящий бэкенд не нужен.
Кнопка-сердечко со счётчиком лайков. По клику число растёт сразу, но запрос иногда падает — и тогда лайк откатывается назад.
Шаги-
Заведи настоящее состояние — число лайков через
useState, например 10. -
Оберни его в
useOptimistic. Функция обновления берёт текущее число и прибавляет 1. -
По клику вызови
addOptimistic()внутриstartTransition, а следом «отправь запрос». -
Запрос — фейковый:
setTimeout, который в 30% случаев завершается ошибкой. При успехе увеличь настоящее состояние на 1. При ошибке не делай ничего — React вернёт число сам.
Подсказкаawait new Promise((res, rej) =>
setTimeout(() => Math.random() < 0.3 ?
rej() : res(), 800))
ГотовоКогда запрос падает, счётчик подпрыгивает на +1 и тут же возвращается на место.
Поле email и кнопка. Пока идёт отправка — кнопка заблокирована и пишет «Отправляем…». Потом либо «Готово», либо ошибка, если email пустой или без символа @.
Шаги-
Напиши async-функцию действия
subscribeAction(prevState, formData). Достань email черезformData.get('email'). -
Проверь его: если пусто или нет
@— верни{ error: 'Введите email' }. Иначе подожди секунду и верни{ ok: true }. -
В компоненте:
const [state, action, isPending] = useActionState(subscribeAction, null). -
Повесь
actionна<form action={action}>, блокируй кнопку поisPending, покажиstate.errorилиstate.ok.
ПодсказкаИмпорт useActionState из
'react', а не из
'react-dom'. Проверку делай прямо
внутри функции действия — отдельный
useState для ошибки больше не
нужен.
ГотовоПустой email даёт ошибку сразу. Правильный — «Отправляем…», затем «Готово».
Вынести кнопку отправки в отдельный компонент, который сам знает, идёт ли отправка формы — без единого пропса. Вставить его в две разные формы.
Шаги-
Сделай компонент
SubmitButton. Внутри —const { pending } = useFormStatus(). Кнопка блокируется и меняет текст поpending. -
Собери две разные формы: у каждой свой
actionи свои поля. -
В каждую форму вставь
<SubmitButton />— ничего ей не передавая. - Отправь по очереди обе формы и посмотри на кнопки.
ПодсказкаuseFormStatus — из
'react-dom'. Кнопка обязана быть
внутри <form>: если
поставить рядом, pending всегда
останется false.
ГотовоОбе кнопки гаснут во время своей отправки, хотя ты не прокидывал им состояние.
Компонент читает данные пользователя через
use(userPromise) и показывает имя и
роль. Пока грузится — Suspense показывает заглушку.
-
Создай промис один раз снаружи компонента
— в модуле или в родителе:
const userPromise = fetchUser(). -
В компоненте
Profile:const user = use(userPromise), выведиuser.nameиuser.role. -
Оберни
<Profile />в<Suspense fallback={<p>Загрузка…</p>}>. -
fetchUser— снова фейк: промис сsetTimeout, который через секунду отдаёт объект пользователя.
ПодсказкаЕсли создать промис прямо в теле компонента, на каждый рендер будет новый промис и бесконечная загрузка. Промис живёт снаружи.
ГотовоСначала видно «Загрузка…», потом — профиль. Спиннер не крутится вечно.