Управляемые поля, FormData, валидация границ, action, useActionState, useFormStatus и оптимистичные изменения.
Форма — транзакционная граница пользовательского ввода, а не набор setter на каждую клавишу по умолчанию.
Вы построите доступную форму с типизированной валидацией, pending/error/success состояниями, защитой от повторной отправки и оптимистичным интерфейсом.
До начала достаточно handlers, state modeling, unknown и discriminated unions. В конце должен появиться не конспект, а форма создания заявки с client/server-валидацией и детерминированным восстановлением после отказа.
<form> объединяет поля, submit с Enter, browser validation и progressive behavior. Submit-логика должна жить на onSubmit/action, а не только на click кнопки: иначе Enter и другие submitters обходят код. Каждый input получает label и осмысленное name, потому что name определяет ключ FormData.
Рабочая проверка. Отправьте форму Enter из текстового поля и клавиатурой активируйте submit.
Типичная поломка. Собрать форму из div и click-handler без нативной семантики.
Controlled input получает value/checked и синхронный onChange; источник истины находится в React state. Uncontrolled использует defaultValue и DOM до чтения формы. Нельзя переключать один input между режимами: controlled value не должен становиться undefined. Выбор зависит от того, нужен ли React каждый промежуточный символ.
Рабочая проверка. Сделайте controlled поиск с живым preview и uncontrolled поле комментария, читаемое при submit.
Типичная поломка. Передать value={maybeString} и получить переход controlled/uncontrolled.
FormData.get возвращает строку, File или null. Числа, даты и boolean не восстанавливаются автоматически. На клиенте и сервере вход нужно разобрать и проверить; TypeScript-тип action не защищает от вручную сформированного HTTP-запроса. Ошибки схемы должны быть доменным результатом, системные сбои — отдельной error policy.
Следующий фрагмент показывает минимальный рабочий контракт, а не заготовку для слепого копирования.
const rawTitle = formData.get('title');
if (typeof rawTitle !== 'string' || rawTitle.trim().length < 3) {
return { status: 'invalid' as const, fields: { title: 'Минимум 3 символа' } };
}Проверка одновременно исключает null/File и применяет доменное правило. Сервер повторяет доверенную валидацию независимо от клиента.
Рабочая проверка. Отправьте отсутствующее поле, File вместо строки и пробельное название.
Типичная поломка. Писать formData.get("title") as string без проверки.
React 19 позволяет передать функцию в action формы. Async Action управляет transition отправки; после успешной отправки uncontrolled form reset происходит автоматически. Action может быть клиентской или Server Function в поддерживающем framework. Она не отменяет необходимость авторизации, идемпотентности и обработки системных ошибок.
Рабочая проверка. Реализуйте action с успешным результатом, validation failure и сетевым отказом.
Типичная поломка. Считать любую функцию с именем action доверенной серверной транзакцией.
useActionState и типизированный результатuseActionState связывает Action с последним возвращённым state и pending-флагом. Функция получает previous state первым аргументом, затем payload. Результат удобно моделировать union: idle | invalid | success | failure. Не возвращайте exception object или внутренние детали сервера пользователю.
Следующий фрагмент показывает минимальный рабочий контракт, а не заготовку для слепого копирования.
type FormState =
| { status: 'idle' }
| { status: 'invalid'; fields: Record<string, string> }
| { status: 'success'; id: string };
const [state, submitAction, pending] = useActionState(saveRequest, { status: 'idle' });Union заставляет JSX явно разобрать каждый пользовательский исход, а pending описывает выполняющуюся Action отдельно от последнего результата.
Рабочая проверка. Свяжите field error с aria-describedby и оставьте пользовательский ввод после validation failure.
Типичная поломка. Использовать string | null одновременно для успеха, ошибки и отсутствия результата.
useFormStatus у потомка формыuseFormStatus читает статус родительской формы, как context. Hook должен вызываться в компоненте, отрендеренном внутри form; тот же компонент, который возвращает form, не читает статус собственной будущей формы. Статус содержит pending и данные текущей отправки, но пользовательский текст и live validation не стоит строить на постоянном чтении FormData.
Следующий фрагмент показывает минимальный рабочий контракт, а не заготовку для слепого копирования.
function SubmitButton() {
const { pending } = useFormStatus();
return <button disabled={pending}>{pending ? 'Сохраняем…' : 'Сохранить'}</button>;
}Кнопка находится внутри <form action={...}>, поэтому получает статус ближайшей формы без отдельного prop drilling.
Рабочая проверка. Добавьте две формы и убедитесь, что pending одной не блокирует кнопку другой.
Типичная поломка. Вызвать useFormStatus рядом с JSX form и ожидать статус этой же формы.
useOptimistic позволяет показать предполагаемый результат на время Action. Transformation должна быть чистой. Каждая optimistic запись получает стабильный client id и статус, чтобы подтверждение сопоставило её с server id, а отказ дал откат или возможность повтора. Нельзя обещать необратимый успех до подтверждения.
Рабочая проверка. Смоделируйте два параллельных optimistic добавления и ответы сервера в обратном порядке.
Типичная поломка. Сопоставлять подтверждение по индексу списка или тексту записи.
Disabled submit уменьшает случайные повторы, но не заменяет идемпотентность сервера: запрос можно повторить сетью, вкладкой или атакующим клиентом. Для create-команд нужен idempotency key или доменная дедупликация. Validation error показывается рядом с полем; системный отказ сохраняет данные и даёт безопасный retry. Не все ошибки следует превращать в «неверные поля».
Рабочая проверка. Дважды отправьте одну команду с тем же idempotency key и проверьте один server result.
Типичная поломка. Считать disabled={pending} гарантией exactly-once.
Реализуйте форму заявки через React 19 action. Прочитайте FormData как unknown, провалидируйте поля, верните field errors через useActionState, покажите pending через useFormStatus, добавьте optimistic запись с временным id и откат при отказе.
Критерий завершения: форма работает с клавиатуры, повторная отправка контролируется, ошибки связаны с полями, а optimistic state согласуется с подтверждённым результатом. Сначала зафиксируйте наблюдаемое поведение тестом или измерением, затем меняйте реализацию.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.
Далее: Проектирование API компонентов