Что такое хук useFormState?

👨‍💻 Frontend Developer 🟠 Может встретиться 🎚️ Средний
#React #Hooks

Краткий ответ

useFormState — это хук React для работы с формами и серверными действиями:

  1. Управление состоянием формы — отслеживает данные и статус 📝
  2. Серверные действия — интеграция с Server Actions 🖥️
  3. Автоматическая обработка — loading, error, success состояния ⚡
  4. Прогрессивное улучшение — работает без JavaScript 🌐
  5. Оптимистичные обновления — мгновенная обратная связь ✨
  6. Валидация — встроенная поддержка ошибок ❌
import { useFormState } from 'react-dom';
 
function ContactForm() {
  const [state, formAction] = useFormState(submitForm, null);
  
  return (
    <form action={formAction}>
      <input name="email" type="email" />
      <button type="submit">Отправить</button>
      {state?.error && <p>{state.error}</p>}
    </form>
  );
}

Полный ответ

useFormState — это современный хук для работы с формами в React! Он упрощает интеграцию с серверными действиями и управление состоянием форм. 🚀

Основной синтаксис

import { useFormState } from 'react-dom';
 
const [state, formAction] = useFormState(action, initialState);

Параметры хука

// action - серверная функция
async function submitForm(prevState, formData) {
  // Обработка данных формы
  const email = formData.get('email');
  
  try {
    await sendEmail(email);
    return { success: true, message: 'Отправлено!' };
  } catch (error) {
    return { error: 'Ошибка отправки' };
  }
}
 
// initialState - начальное состояние
const initialState = { message: null, error: null };
 
function MyForm() {
  const [state, formAction] = useFormState(submitForm, initialState);
  
  return (
    <form action={formAction}>
      <input name="email" required />
      <button type="submit">Отправить</button>
      
      {state?.success && <p>✅ {state.message}</p>}
      {state?.error && <p>❌ {state.error}</p>}
    </form>
  );
}

Практические примеры

1. Форма обратной связи

async function contactAction(prevState, formData) {
  const name = formData.get('name');
  const email = formData.get('email');
  const message = formData.get('message');
  
  // Валидация
  if (!name || !email || !message) {
    return { error: 'Заполните все поля' };
  }
  
  try {
    await sendContactForm({ name, email, message });
    return { success: true, message: 'Сообщение отправлено!' };
  } catch (error) {
    return { error: 'Ошибка сервера' };
  }
}
 
function ContactForm() {
  const [state, formAction] = useFormState(contactAction, null);
  
  return (
    <form action={formAction}>
      <input name="name" placeholder="Имя" required />
      <input name="email" type="email" placeholder="Email" required />
      <textarea name="message" placeholder="Сообщение" required />
      
      <button type="submit">Отправить</button>
      
      {state?.success && (
        <div className="success">✅ {state.message}</div>
      )}
      {state?.error && (
        <div className="error">❌ {state.error}</div>
      )}
    </form>
  );
}

2. Форма регистрации

async function registerAction(prevState, formData) {
  const username = formData.get('username');
  const password = formData.get('password');
  
  // Валидация
  if (password.length < 6) {
    return { error: 'Пароль должен быть минимум 6 символов' };
  }
  
  try {
    const user = await createUser({ username, password });
    return { success: true, user };
  } catch (error) {
    return { error: 'Пользователь уже существует' };
  }
}
 
function RegisterForm() {
  const [state, formAction] = useFormState(registerAction, null);
  
  return (
    <form action={formAction}>
      <input name="username" placeholder="Логин" required />
      <input name="password" type="password" placeholder="Пароль" required />
      
      <button type="submit">Зарегистрироваться</button>
      
      {state?.success && (
        <p>Добро пожаловать, {state.user.username}!</p>
      )}
      {state?.error && <p className="error">{state.error}</p>}
    </form>
  );
}

3. Форма с загрузкой файлов

async function uploadAction(prevState, formData) {
  const file = formData.get('file');
  
  if (!file || file.size === 0) {
    return { error: 'Выберите файл' };
  }
  
  if (file.size > 5 * 1024 * 1024) {
    return { error: 'Файл слишком большой (макс. 5MB)' };
  }
  
  try {
    const url = await uploadFile(file);
    return { success: true, url };
  } catch (error) {
    return { error: 'Ошибка загрузки файла' };
  }
}
 
function UploadForm() {
  const [state, formAction] = useFormState(uploadAction, null);
  
  return (
    <form action={formAction}>
      <input name="file" type="file" accept="image/*" required />
      <button type="submit">Загрузить</button>
      
      {state?.success && (
        <div>
          <p>✅ Файл загружен!</p>
          <img src={state.url} alt="Uploaded" width="200" />
        </div>
      )}
      {state?.error && <p className="error">{state.error}</p>}
    </form>
  );
}

Интеграция с useFormStatus

import { useFormState } from 'react-dom';
import { useFormStatus } from 'react-dom';
 
function SubmitButton() {
  const { pending } = useFormStatus();
  
  return (
    <button type="submit" disabled={pending}>
      {pending ? 'Отправка...' : 'Отправить'}
    </button>
  );
}
 
function MyForm() {
  const [state, formAction] = useFormState(submitAction, null);
  
  return (
    <form action={formAction}>
      <input name="data" required />
      <SubmitButton />
      
      {state?.error && <p>{state.error}</p>}
    </form>
  );
}

Работа с валидацией

async function validateAction(prevState, formData) {
  const email = formData.get('email');
  const age = formData.get('age');
  
  const errors = {};
  
  // Валидация email
  if (!email.includes('@')) {
    errors.email = 'Неверный формат email';
  }
  
  // Валидация возраста
  if (age < 18) {
    errors.age = 'Возраст должен быть 18+';
  }
  
  if (Object.keys(errors).length > 0) {
    return { errors };
  }
  
  // Сохранение данных
  await saveUser({ email, age });
  return { success: true };
}
 
function ValidationForm() {
  const [state, formAction] = useFormState(validateAction, null);
  
  return (
    <form action={formAction}>
      <div>
        <input name="email" type="email" placeholder="Email" />
        {state?.errors?.email && (
          <span className="error">{state.errors.email}</span>
        )}
      </div>
      
      <div>
        <input name="age" type="number" placeholder="Возраст" />
        {state?.errors?.age && (
          <span className="error">{state.errors.age}</span>
        )}
      </div>
      
      <button type="submit">Сохранить</button>
      
      {state?.success && <p>✅ Данные сохранены!</p>}
    </form>
  );
}

Оптимистичные обновления

import { useOptimistic } from 'react';
 
async function addCommentAction(prevState, formData) {
  const comment = formData.get('comment');
  
  // Имитация задержки сервера
  await new Promise(resolve => setTimeout(resolve, 1000));
  
  const newComment = {
    id: Date.now(),
    text: comment,
    author: 'Пользователь'
  };
  
  return { success: true, comment: newComment };
}
 
function CommentForm({ comments, onAddComment }) {
  const [state, formAction] = useFormState(addCommentAction, null);
  const [optimisticComments, addOptimisticComment] = useOptimistic(
    comments,
    (state, newComment) => [...state, newComment]
  );
  
  return (
    <div>
      <form 
        action={async (formData) => {
          const comment = formData.get('comment');
          addOptimisticComment({
            id: Date.now(),
            text: comment,
            author: 'Пользователь'
          });
          
          const result = await formAction(formData);
          if (result?.success) {
            onAddComment(result.comment);
          }
        }}
      >
        <textarea name="comment" placeholder="Ваш комментарий" />
        <button type="submit">Добавить</button>
      </form>
      
      <div>
        {optimisticComments.map(comment => (
          <div key={comment.id}>{comment.text}</div>
        ))}
      </div>
    </div>
  );
}

Преимущества useFormState

  1. Прогрессивное улучшение — работает без JS 🌐
  2. Простота использования — минимум кода ✨
  3. Автоматическая обработка — состояния loading/error 🔄
  4. SEO-дружелюбность — серверный рендеринг 🔍
  5. Типобезопасность — с TypeScript 🛡️

Лучшие практики

  1. Валидируйте данные на сервере и клиенте ✅
  2. Обрабатывайте ошибки корректно ❌
  3. Показывайте состояние загрузки пользователю ⏳
  4. Используйте оптимистичные обновления для UX ⚡
// ✅ Правильно
async function goodAction(prevState, formData) {
  try {
    // Валидация
    const data = validateFormData(formData);
    
    // Обработка
    const result = await processData(data);
    
    return { success: true, data: result };
  } catch (error) {
    return { error: error.message };
  }
}

Частые ошибки

Неправильно:

// Забыли обработать ошибки
async function badAction(prevState, formData) {
  const result = await api.call(formData); // Может упасть
  return result;
}

Правильно:

// Правильная обработка ошибок
async function goodAction(prevState, formData) {
  try {
    const result = await api.call(formData);
    return { success: true, data: result };
  } catch (error) {
    return { error: 'Что-то пошло не так' };
  }
}

Заключение

useFormState — мощный инструмент для работы с формами:

  • Упрощает интеграцию с серверными действиями
  • Автоматически управляет состоянием формы
  • Поддерживает прогрессивное улучшение
  • Улучшает пользовательский опыт

Используйте его для создания современных и отзывчивых форм! 🎯