JavaRush /Курсы /Модуль 4: Node.js, Next.js и Angular /Передача данных формы в Server Actions

Передача данных формы в Server Actions

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

1. Почему это вообще работает?

Как вы уже знаете, в Next.js 15 с App Router и Server Actions появился новый, очень удобный паттерн: вы описываете функцию с "use server", а затем просто указываете её как action у формы. Когда пользователь отправляет форму, Next.js автоматически сериализует все данные полей формы и передаёт их на сервер, где вызывает вашу Server Action с этими данными.

Выглядит магически, но под капотом всё честно:

  • Браузер отправляет POST-запрос (обычно с Content-Type: application/x-www-form-urlencoded или multipart/form-data).
  • Next.js парсит этот запрос и превращает данные формы в объект FormData.
  • Ваш Server Action получает этот объект и делает с ним всё, что душе угодно.

Пример базовой формы и Server Action


// actions.js
"use server";

export async function handleForm(formData) {
  const name = formData.get('name'); // Получаем значение поля name
  const email = formData.get('email');
  // Здесь можно делать всё, что угодно: валидация, запись в БД, отправка писем
}

// page.jsx
import { handleForm } from './actions';

export default function ContactForm() {
  return (
    <form action={handleForm}>
      <input name="name" placeholder="Имя" />
      <input name="email" placeholder="Email" type="email" />
      <button type="submit">Отправить</button>
    </form>
  );
}

Важно!
Server Action всегда получает первым аргументом объект FormData (или, если используете <input type="file">, то и файлы тоже).

2. Объект FormData: как с ним работать

FormData — это специальный объект, который содержит пары "ключ-значение" для всех полей формы. Если у вас есть поле <input name="name" value="Вася" />, то formData.get('name') вернёт "Вася".

Основные методы FormData

  • formData.get(name) — получить значение первого поля с таким именем.
  • formData.getAll(name) — получить массив всех значений (актуально для чекбоксов, select multiple и т.п.).
  • formData.has(name) — проверить, есть ли поле с таким именем.

Пример: разные типы полей


<form action={handleForm}>
  <input name="name" value="Вася" />
  <input name="age" value="30" type="number" />
  <input name="subscribe" type="checkbox" value="yes" checked />
  <input name="colors" value="red" type="checkbox" checked />
  <input name="colors" value="green" type="checkbox" />
  <button type="submit">Отправить</button>
</form>

export async function handleForm(formData) {
  const name = formData.get('name'); // "Вася"
  const age = formData.get('age');   // "30" (всегда строка!)
  const subscribe = formData.get('subscribe'); // "yes" (если чекбокс отмечен)
  const colors = formData.getAll('colors'); // ["red"] (если только red отмечен)
}

Обратите внимание:
Все значения, получаемые из FormData, — это строки (или File, если это файл). Даже если поле типа number, вы получите строку "30". Не забудьте привести к нужному типу!

3. Передача сложных данных: массивы, чекбоксы, select multiple

Массивы (например, несколько чекбоксов)

Если у вас несколько полей с одинаковым именем, например:


<input type="checkbox" name="hobbies" value="reading" />
<input type="checkbox" name="hobbies" value="coding" />
<input type="checkbox" name="hobbies" value="gaming" />

и пользователь выбрал "coding" и "gaming", то:

const hobbies = formData.getAll('hobbies'); // ["coding", "gaming"]

Select multiple


<select name="fruits" multiple>
  <option value="apple">Яблоко</option>
  <option value="banana">Банан</option>
  <option value="cherry">Вишня</option>
</select>

Если выбрано "apple" и "cherry":


const fruits = formData.getAll('fruits'); // ["apple", "cherry"]

Передача файлов

Работа с файлами (например, <input type="file" name="avatar" />) ничем не отличается — в FormData попадёт объект File.


export async function handleForm(formData) {
  const avatar = formData.get('avatar'); // File или null
  if (avatar && avatar.size > 0) {
    // Можно сохранить файл, например, через fs/promises или cloud storage
  }
}

Внимание:
- Для передачи файлов форма должна иметь enctype="multipart/form-data".
- В React это не обязательно указывать — браузер сам подставит нужный тип, если есть <input type="file">.

4. Пример: обработка формы регистрации пользователя

Давайте соберём вместе всё, что знаем, и реализуем форму регистрации с несколькими полями, чекбоксом и валидацией на сервере.


// actions.js
"use server";

export async function registerUser(formData) {
  const username = formData.get('username')?.trim();
  const email = formData.get('email')?.trim();
  const password = formData.get('password');
  const agree = formData.get('agree'); // "on" если чекбокс отмечен

  // Простая валидация
  if (!username || !email || !password) {
    throw new Error("Все поля обязательны!");
  }
  if (agree !== "on") {
    throw new Error("Необходимо принять условия!");
  }

  // Здесь можно добавить запись в базу, отправку письма и т.д.
  return { success: true, username };
}

// RegisterForm.jsx
import { registerUser } from './actions';

export default function RegisterForm() {
  return (
    <form action={registerUser}>
      <input name="username" placeholder="Имя пользователя" required />
      <input name="email" type="email" placeholder="Email" required />
      <input name="password" type="password" placeholder="Пароль" required />
      <label>
        <input type="checkbox" name="agree" required /> Я принимаю условия
      </label>
      <button type="submit">Зарегистрироваться</button>
    </form>
  );
}

5. Как обрабатывать результат Server Action

Server Actions могут возвращать любые сериализуемые данные (например, объект с результатом, ошибкой, новым состоянием). В простом случае результат никак не используется, но с хуками вроде useFormState (см. предыдущие лекции) можно отображать сообщения об успехе или ошибке.


// actions.js
"use server";

export async function registerUser(formData) {
  // ...как выше
  return { ok: true, message: "Регистрация прошла успешно!" };
}

// RegisterForm.jsx
'use client';

import { useFormState } from 'react-dom';
import { registerUser } from './actions';

export default function RegisterForm() {
  const [state, formAction] = useFormState(registerUser, { ok: false });

  return (
    <form action={formAction}>
      {/* ...поля */}
      <button type="submit">Зарегистрироваться</button>
      {state?.message && <div>{state.message}</div>}
    </form>
  );
}

6. Особенности и ограничения передачи данных

  • Всё, что не <input name="..."> или <select name="...">, не попадёт в FormData. Если забыли указать name, поле не отправится!
  • Типы данных: Всё, что не файл, передаётся как строка. Даже если поле <input type="number" ... />, вы получите строку. Не забывайте преобразовывать к нужному типу (Number(value)).
  • Checkbox: Если чекбокс не отмечен — его нет в FormData вообще. Если отмечен, то значение — из value, либо "on" по умолчанию.
  • Radio: Только выбранный radio попадает в FormData.
  • Файлы: Для передачи файлов используйте <input type="file"> и соответствующую обработку на сервере.
  • Массивы: Для передачи нескольких значений используйте одинаковое имя для всех полей (name="tags" у каждого чекбокса).

7. Типичные ошибки при передаче данных формы в Server Actions

Ошибка №1: Не указано имя поля (name).
Если у <input> или <select> нет атрибута name, оно не попадёт в FormData. Проверьте внимательно разметку!

Ошибка №2: Ожидание числа, а приходит строка.
Все значения из FormData — строки. Если ждёте число, делайте явное преобразование через Number(value) или parseInt(value, 10).

Ошибка №3: Чекбокс не отмечен — ключа нет в FormData.
Если чекбокс не отмечен, он вообще не попадёт в FormData. Не пытайтесь сделать formData.get('subscribe') === false — лучше проверяйте formData.has('subscribe').

Ошибка №4: Несколько значений (чекбоксы, select multiple) — забыли использовать getAll.
Если у вас несколько чекбоксов с одним именем, используйте formData.getAll('имя'), иначе получите только первое значение.

Ошибка №5: Попытка получить поле, которого нет.
Если такого поля не было, formData.get('что-то') вернёт null. Не забывайте обрабатывать этот случай.

Ошибка №6: Неправильная обработка файлов.
Если используете <input type="file">, не забывайте, что значение — это объект File, а не строка. Проверяйте размер, тип и т.д.

Ошибка №7: Забыли обработать ошибки в Server Action.
Если Server Action выбрасывает ошибку, клиент получит generic сообщение. Для красивой обработки используйте useFormState и возвращайте объект с ошибкой.

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