Типизация FormAction и useActionState в React 19: строгая валидация серверных ошибок и ...
Типизация FormAction и useActionState в React 19: строгая валидация серверных ошибок и optimistic updates с discriminated unions useActionState в React 19 штука удобная. Но без нормальной типизации форма превращается в угадайку. Сервер может вернуть что угодно: { error: "Email exists" } или { message: "Validation failed", field: "email" }. И сиди потом гадай, что пришло. Пишут if ("error" in data), а это прямой путь к багам. Discriminated union для состояний формы Лучше сделать discriminated union с дискриминантом status. Тогда каждое состояние формы описано строго: type FormState = | { status: 'idle' } | { status: 'loading' } | { status: 'success'; message: string } | { status: 'error'; fieldErrors: Record<string, string[]> }; const action = async (prev: FormState, formData: FormData): Promise<FormState> => { const name = formData.get('name') as string; if (!name) return { status: 'error', fieldErrors: { name: ['Required'] } }; return { status: 'success', message: 'Saved!' }; }; Тип prev и возврат совпадают. В состоянии success не получится обратиться к fieldErrors - TypeScript сам не даст. Ошибка на этапе компиляции, а не в рантайме. Optimistic updates с временным состоянием Добавляем временное состояние для немедленного отклика: type OptimisticState = FormState | { status: 'optimistic'; name: string }; // В рендере: if (state.status === 'optimistic') return <div>Предпросмотр: {state.name}</div>; Явный тип FormAction Если хочется явно объявить сигнатуру: type FormAction = (prev: FormState, formData: FormData) => Promise<FormState>; Типичная ошибка: использовать any для состояния формы. Это убивает всю строгость и переносит проверки в рантайм. Практический совет: всегда начинайте с discriminated union - это даёт полную безопасность типов при работе с серверными ошибками и побочными эффектами. Вывод: Discriminated unions с useActionState отсекают целый класс багов на этапе компиляции, делая код формы предсказуемым и устойчивым к изменениям API.