Формы с действиями
В React 19 у формы появился новый способ отправки: в атрибут action можно передать функцию. React сам соберёт значения полей в объект FormData, вызовет функцию и после успешного завершения очистит поля. Для состояния ожидания и результата есть хук useActionState. Состояние для каждого поля при этом заводить не нужно.
Функция в action
Добавим в блог форму новой статьи. Создайте src/pages/NewPostPage.jsx:
// src/pages/NewPostPage.jsx
export default function NewPostPage() {
function createPost(formData) {
console.log('Заголовок:', formData.get('title'));
}
return (
<form action={createPost}>
<input name="title" placeholder="Заголовок" />
<button type="submit">Опубликовать</button>
</form>
);
}Добавьте маршрут <Route path="posts/new" element={<NewPostPage />} /> в src/App.jsx и откройте /posts/new. После отправки в консоли появится введённый заголовок, страница не перезагрузится, а поле очистится. Значение берётся по атрибуту name поля.
useActionState: результат и ожидание
Хук useActionState принимает функцию-действие и начальное состояние. Он возвращает текущее состояние, функцию для action и признак выполнения:
// src/pages/NewPostPage.jsx
import { useActionState } from 'react';
async function createPost(prevState, formData) {
const title = formData.get('title');
if (!title) {
return { error: 'Введите заголовок' };
}
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title, body: formData.get('body'), userId: 1 }),
});
const post = await response.json();
return { message: `Статья создана, номер ${post.id}` };
}
export default function NewPostPage() {
const [state, formAction, isPending] = useActionState(createPost, {});
return (
<form action={formAction}>
<input name="title" placeholder="Заголовок" />
<textarea name="body" placeholder="Текст" />
<button type="submit" disabled={isPending}>
{isPending ? 'Отправляем…' : 'Опубликовать'}
</button>
{state.error && <p>{state.error}</p>}
{state.message && <p>{state.message}</p>}
</form>
);
}Функция-действие получает первым аргументом предыдущее состояние, вторым — данные формы, а то, что она вернёт, станет новым state. Отправьте пустую форму — появится «Введите заголовок». Заполните заголовок — кнопка на время запроса покажет «Отправляем…», затем появится «Статья создана, номер 101». JSONPlaceholder только имитирует создание и всегда отвечает номером 101.
useFormStatus: состояние в дочернем компоненте
Кнопку отправки удобно вынести в отдельный компонент. Узнать, отправляется ли форма, он может хуком useFormStatus из пакета react-dom:
// src/components/SubmitButton.jsx
import { useFormStatus } from 'react-dom';
export default function SubmitButton({ children }) {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? 'Отправляем…' : children}
</button>
);
}В форме: <SubmitButton>Опубликовать</SubmitButton>. Кнопка сама становится неактивной во время отправки.
Действия или onSubmit
onSubmit и состояние полей | action и useActionState |
|---|---|
| Нужно проверять ввод на лету | Достаточно проверки при отправке |
| Поля зависят друг от друга | Поля независимы |
| Работает в любой версии React | Нужен React 19 |
Частая ошибка
useFormStatus возвращает данные только той формы, внутри которой стоит компонент. Если вызвать хук в самом компоненте с формой, pending всегда будет false:
// Неправильно: форма находится ниже, в разметке этого же компонента.
export default function NewPostPage() {
const { pending } = useFormStatus();
return <form action={createPost}>...</form>;
}Вызывайте useFormStatus в дочернем компоненте внутри form, как SubmitButton выше, или берите isPending из useActionState.