Encodera

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

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

Оглавление

Проверка формы и вывод ошибок

Данные из формы нельзя сохранять как есть: посетитель может оставить поле пустым или ввести адрес с пробелами. Проверку (валидацию) делают в серверном действии, а ошибки возвращают обратно в форму. Связывает действие и форму хук useActionState из React 19.

Действие возвращает ошибки

Перепишите app/posts/actions.ts. Теперь действие принимает первым аргументом предыдущее состояние и возвращает новое:

// app/posts/actions.ts
'use server';
 
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
import { addPost } from '@/lib/posts';
 
export type FormState = {
  errors: { title?: string; slug?: string; body?: string };
};
 
export async function createPost(prevState: FormState, formData: FormData): Promise<FormState> {
  const title = String(formData.get('title') ?? '').trim();
  const slug = String(formData.get('slug') ?? '').trim();
  const body = String(formData.get('body') ?? '').trim();
 
  const errors: FormState['errors'] = {};
  if (title.length < 3) errors.title = 'Заголовок — не короче 3 символов';
  if (!/^[a-z0-9-]+$/.test(slug)) errors.slug = 'Только латинские буквы, цифры и дефис';
  if (body === '') errors.body = 'Напишите текст статьи';
 
  if (Object.keys(errors).length > 0) {
    return { errors };
  }
 
  await addPost({ slug, title, body });
  revalidatePath('/posts');
  redirect(`/posts/${slug}`);
}

Если есть ошибки, действие возвращает их объектом и ничего не сохраняет. Если ошибок нет — сохраняет статью и перенаправляет, как раньше.

Форма с useActionState

Хук работает только в клиентском компоненте, поэтому форму вынесем в components/NewPostForm.tsx:

// components/NewPostForm.tsx
'use client';
 
import { useActionState } from 'react';
import { createPost, type FormState } from '@/app/posts/actions';
 
const initialState: FormState = { errors: {} };
 
export default function NewPostForm() {
  const [state, formAction, isPending] = useActionState(createPost, initialState);
 
  return (
    <form action={formAction}>
      <input name="title" placeholder="Заголовок" />
      {state.errors.title && <p>{state.errors.title}</p>}
 
      <input name="slug" placeholder="Адрес" />
      {state.errors.slug && <p>{state.errors.slug}</p>}
 
      <textarea name="body" placeholder="Текст статьи" />
      {state.errors.body && <p>{state.errors.body}</p>}
 
      <button type="submit" disabled={isPending}>
        {isPending ? 'Сохраняем...' : 'Опубликовать'}
      </button>
    </form>
  );
}

useActionState возвращает три значения: текущее состояние (сначала initialState, потом результат действия), функцию для атрибута action и флаг isPending, который равен true, пока действие выполняется.

Подключение к странице

// app/posts/new/page.tsx
import NewPostForm from '@/components/NewPostForm';
 
export default function NewPostPage() {
  return (
    <main>
      <h1>Новая статья</h1>
      <NewPostForm />
    </main>
  );
}

Откройте /posts/new и отправьте пустую форму — под полями появятся три сообщения, а кнопка на время запроса станет неактивной.

Библиотеки проверки

Когда полей много, правила описывают схемой в библиотеке вроде Zod, а не цепочкой if. Принцип тот же: проверка на сервере, ошибки в состоянии.

Частая ошибка

Удивляться, что после ошибки поля формы очистились. В React 19 форма с действием сбрасывается после каждой отправки. Чтобы введённый текст не пропадал, верните значения в состоянии вместе с ошибками и подставьте их в поля через defaultValue, например defaultValue={state.values.title}.

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

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