JavaRush /Курсы /Модуль 4: Node.js, Next.js и Angular /useFormState: управление статусом формы, обработка ошибок...

useFormState: управление статусом формы, обработка ошибок

Модуль 4: Node.js, Next.js и Angular
10 уровень , 5 лекция
Открыта

1. Введение

Давайте честно: отправка формы — это только полдела. Пользователь хочет видеть, что происходит: всё ли получилось, что не так, если возникла ошибка, или наоборот — поздравление с успехом. И конечно, хочется сразу подсвечивать ошибки валидации, чтобы не пришлось гадать, почему сервер молчит.

В "старых добрых" React-приложениях мы писали кучу кода для управления состоянием формы: хранили значения полей, ошибки, статусы загрузки, отправляли данные через fetch/AJAX и т.д.

В мире Server Actions всё стало проще, но управление состоянием всё равно нужно. Вот тут и появляется useFormState — хук, который позволяет:

  • Хранить и обновлять состояние формы (например, ошибки, сообщения, результат отправки).
  • Реагировать на результат Server Action прямо в клиентском компоненте.
  • Делать формы интерактивными без сложных костылей.

Как работает useFormState?

useFormState — это React-хук, который связывает состояние формы с серверным action. Он принимает два аргумента:

  1. Action — серверная функция (Server Action), которая будет вызываться при отправке формы.
  2. InitialState — начальное состояние формы (например, { message: '' }, { errors: {} } и т.д.).

Хук возвращает массив из двух элементов:

  • state — текущее состояние формы (например, ошибки, сообщения).
  • formAction — функция, которую нужно передать в атрибут action формы.

Вот базовый синтаксис:

const [state, formAction] = useFormState(serverAction, initialState);

state обновляется автоматически после каждого отправления формы — на основании того, что возвращает ваша Server Action.

2. Простой пример: форма обратной связи

Давайте создадим простую форму отправки сообщения с обработкой ошибок через useFormState.

Серверный action


// app/actions/sendMessage.js
"use server";

export async function sendMessage(prevState, formData) {
  const message = formData.get("message");
  if (!message || message.length < 5) {
    return { error: "Сообщение должно быть не короче 5 символов" };
  }
  // Здесь могла бы быть отправка в базу данных...
  return { success: "Спасибо за сообщение!" };
}

Клиентский компонент с useFormState


// app/components/ContactForm.jsx
"use client";

import { useFormState } from "react-dom";
import { sendMessage } from "../actions/sendMessage";

const initialState = {};

export default function ContactForm() {
  const [state, formAction] = useFormState(sendMessage, initialState);

  return (
    <form action={formAction}>
      <textarea name="message" placeholder="Ваше сообщение" />
      <button type="submit">Отправить</button>

      {/* Вывод сообщений об ошибке или успехе */}
      {state.error && <div style={{ color: "red" }}>{state.error}</div>}
      {state.success && <div style={{ color: "green" }}>{state.success}</div>}
    </form>
  );
}

Что здесь происходит?

  • При отправке формы вызывается серверная функция sendMessage.
  • Функция возвращает объект с ошибкой или успехом.
  • useFormState автоматически обновляет state, и мы можем отобразить пользователю нужное сообщение.

Как работает передача состояния

Вся магия useFormState строится на том, что серверная функция (Server Action) всегда принимает два аргумента:

  • prevState — предыдущее состояние формы (например, ошибки, значения).
  • formData — данные, пришедшие из формы.

Server Action возвращает новое состояние, которое автоматически попадает в state на клиенте.

Важно: useFormState работает только с формами, которые отправляются через Server Actions (action={formAction}), а не через обычные fetch-запросы.

3. Валидация и обработка ошибок

Один из главных плюсов useFormState — простота обработки ошибок. Ваша Server Action может возвращать любые данные: ошибки, поля, сообщения и т.д. На клиенте вы просто отображаете их.

Пример: форма регистрации с валидацией


// app/actions/register.js
"use server";

export async function register(prevState, formData) {
  const email = formData.get("email");
  const password = formData.get("password");
  const errors = {};

  if (!email || !email.includes("@")) {
    errors.email = "Некорректный email";
  }
  if (!password || password.length < 6) {
    errors.password = "Пароль слишком короткий";
  }

  if (Object.keys(errors).length > 0) {
    return { errors };
  }

  // Здесь могла бы быть регистрация пользователя...
  return { success: "Регистрация успешна!" };
}

// app/components/RegisterForm.jsx
"use client";

import { useFormState } from "react-dom";
import { register } from "../actions/register";

const initialState = {};

export default function RegisterForm() {
  const [state, formAction] = useFormState(register, initialState);

  return (
    <form action={formAction}>
      <div>
        <input name="email" placeholder="Email" />
        {state.errors?.email && (
          <span style={{ color: "red" }}>{state.errors.email}</span>
        )}
      </div>
      <div>
        <input name="password" type="password" placeholder="Пароль" />
        {state.errors?.password && (
          <span style={{ color: "red" }}>{state.errors.password}</span>
        )}
      </div>
      <button type="submit">Зарегистрироваться</button>
      {state.success && <div style={{ color: "green" }}>{state.success}</div>}
    </form>
  );
}

Обратите внимание: ошибки для каждого поля выводятся рядом с соответствующим input. После успешной отправки появляется сообщение об успехе.

4. Под капотом: как работает useFormState

Маленькое лирическое отступление для любознательных.

Когда вы используете useFormState, Next.js сам заботится о:

  • Передаче состояния между клиентом и сервером.
  • Ре-рендере компонента после получения ответа от Server Action.
  • Безопасности: состояния не могут быть подделаны на клиенте.

Важно: useFormState работает только в client components (компонентах с "use client").

5. Сброс формы после успешной отправки

Частый вопрос: как очистить поля формы после успешной отправки? useFormState сам не сбрасывает значения input'ов, потому что они неконтролируемые (uncontrolled). Нужно сделать это вручную.

Один из вариантов — использовать ref:


import { useRef } from "react";
import { useFormState } from "react-dom";
import { sendMessage } from "../actions/sendMessage";

export default function ContactForm() {
  const [state, formAction] = useFormState(sendMessage, {});
  const formRef = useRef();

  // Сбросить форму после успешной отправки
  React.useEffect(() => {
    if (state.success && formRef.current) {
      formRef.current.reset();
    }
  }, [state.success]);

  return (
    <form ref={formRef} action={formAction}>
      <textarea name="message" placeholder="Ваше сообщение" />
      <button type="submit">Отправить</button>
      {state.error && <div style={{ color: "red" }}>{state.error}</div>}
      {state.success && <div style={{ color: "green" }}>{state.success}</div>}
    </form>
  );
}

6. Практические советы и типовые сценарии

Когда использовать useFormState

  • Когда нужна серверная валидация и обработка ошибок.
  • Когда важно отобразить результат действия сразу в UI.
  • Для простых форм, которые не требуют сложного управления локальным состоянием.

Когда не стоит использовать useFormState

  • Если форма полностью работает на клиенте (например, сложная валидация "на лету").
  • Если нужно управлять каждым полем формы вручную (controlled inputs).

Советы по организации состояния

  • Возвращайте из Server Action только нужные данные (ошибки, сообщения, поля).
  • Не бойтесь возвращать сложные объекты — useFormState всё корректно обработает.
  • Для сложных форм используйте вложенные объекты для ошибок: { errors: { email: "...", password: "..." } }.

Обработка ошибок, связанных с сервером

Иногда на сервере может произойти "непредвиденная неприятность": база данных недоступна, внешний сервис не отвечает и т.д. Вы можете вернуть ошибку в state или выбросить исключение.

Рекомендуется: возвращать ошибки как часть состояния, чтобы пользователь мог их увидеть.


export async function sendMessage(prevState, formData) {
  try {
    // ...ваша логика
  } catch (err) {
    return { error: "Что-то пошло не так. Попробуйте позже." };
  }
}

7. Типичные ошибки при работе с useFormState

Ошибка №1: попытка использовать useFormState в Server Component.
useFormState работает только в компонентах с "use client". Если забыли эту директиву — будет ошибка.

Ошибка №2: неправильный возврат данных из Server Action.
Если серверная функция возвращает не объект, а, например, строку — на клиенте будет не то, что вы ожидаете. Всегда возвращайте объект с нужными полями.

Ошибка №3: забыли передать formAction в атрибут action формы.
Если написать <form action={serverAction}>, а не <form action={formAction}>, useFormState не будет работать как надо.

Ошибка №4: полагаетесь на автоматический сброс полей формы.
useFormState не сбрасывает значения input'ов автоматически. Если нужно — делайте это вручную через ref и reset.

Ошибка №5: забыли обработать ошибки.
Если не выводить сообщения из state, пользователь не узнает, что пошло не так.

Ошибка №6: используете controlled inputs без onChange.
Если хотите контролировать значения полей вручную, используйте useState и onChange, но тогда useFormState не поможет с их сбросом.

Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ