Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Формы с действиями

В 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.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.