JavaRush /Курсы /Модуль 4: Node.js, Next.js и Angular /Использование Server Actions для мутаций

Использование Server Actions для мутаций

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

1. Добавление элемента

В мире фронтенда и бэкенда под мутацией (mutation) обычно понимают любое изменение данных: создание, обновление или удаление записей. В Next.js 15 Server Actions дают нам простой и быстрый способ реализовать такие операции прямо внутри компонентов — без ручного написания fetch-запросов, API-роутов и лишней возни с передачей данных.

Примеры мутаций:

  • Добавление новой задачи в список дел (ToDo).
  • Удаление или редактирование заметки.
  • Регистрация пользователя.
  • Изменение профиля.
  • Отправка сообщения.

В этой лекции мы рассмотрим, как реализовать такие сценарии на практике, шаг за шагом.

Пример: Добавление задачи в ToDo

Начнём с классики — простой формы добавления задачи. Представим, что у нас есть массив задач, и мы хотим добавить новую задачу через форму.

Шаг 1. Описание структуры данных

Для примера будем хранить задачи в простом массиве в памяти (в реальном проекте, конечно, вы бы работали с базой данных, но для понимания Server Actions этого достаточно).


// app/_data/tasks.js
export const tasks = [
  { id: 1, text: "Купить хлеб" },
  { id: 2, text: "Позвонить маме" },
];

Шаг 2. Объявляем Server Action

Создаём Server Action для добавления задачи:


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

import { tasks } from "../_data/tasks";

export async function addTask(formData) {
  // Получаем текст задачи из формы
  const text = formData.get("text");
  if (!text) return; // Можно добавить валидацию

  // Добавляем задачу в массив (в реальной жизни — в базу)
  const newTask = { id: Date.now(), text };
  tasks.push(newTask);

  // Можно возвращать что-то для оптимистичного UI
  return newTask;
}

Важное замечание: Здесь мы мутируем массив в памяти — это работает только для демонстрации на сервере разработки. В реальном приложении вы бы делали запись в базу данных.

Шаг 3. Форма с Server Action

В компоненте страницы подключаем нашу Server Action к форме:


// app/page.jsx
import { addTask } from "./actions/addTask";
import { tasks } from "./_data/tasks";

export default function TodoPage() {
  return (
    <main>
      <h1>Список задач</h1>
      <form action={addTask}>
        <input name="text" placeholder="Новая задача" required />
        <button type="submit">Добавить</button>
      </form>
      <ul>
        {tasks.map(task => (
          <li key={task.id}>{task.text}</li>
        ))}
      </ul>
    </main>
  );
}

Что происходит?

  • Пользователь вводит задачу и отправляет форму.
  • Next.js сериализует данные формы и отправляет их на сервер.
  • Функция addTask срабатывает на сервере, добавляет задачу в массив.
  • После мутации страница перерисовывается, и новый элемент появляется в списке.

2. Мутация с удалением: удаляем элемент

Шаг 1. Server Action для удаления


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

import { tasks } from "../_data/tasks";

export async function deleteTask(formData) {
  const id = Number(formData.get("id"));
  // Фильтруем задачи, оставляя все кроме удаляемой
  const index = tasks.findIndex(task => task.id === id);
  if (index !== -1) {
    tasks.splice(index, 1);
  }
}

Шаг 2. Кнопка удаления на каждой задаче

В компоненте страницы добавим форму для удаления:


import { deleteTask } from "./actions/deleteTask";
// ... остальной импорт

export default function TodoPage() {
  return (
    <main>
      {/* ... форма добавления */}
      <ul>
        {tasks.map(task => (
          <li key={task.id}>
            {task.text}
            <form action={deleteTask} style={{ display: "inline" }}>
              <input type="hidden" name="id" value={task.id} />
              <button type="submit" aria-label="Удалить">✖️</button>
            </form>
          </li>
        ))}
      </ul>
    </main>
  );
}

Обратите внимание: Для каждой задачи появляется кнопка "Удалить", которая обёрнута в отдельную форму, отправляющую id задачи на сервер.

3. Мутация с обновлением: редактирование элемента

Теперь добавим возможность редактировать задачу. Для этого потребуется:

  • Форма редактирования.
  • Server Action для обновления значения.

Шаг 1. Server Action для обновления


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

import { tasks } from "../_data/tasks";

export async function updateTask(formData) {
  const id = Number(formData.get("id"));
  const text = formData.get("text");
  const task = tasks.find(task => task.id === id);
  if (task && text) {
    task.text = text;
  }
}

Шаг 2. Форма редактирования

Добавим простейший способ редактирования — пусть это будет отдельная форма под списком задач (в реальных приложениях часто делают "инлайн" редактирование, но для простоты оставим так):


import { updateTask } from "./actions/updateTask";
// ... остальной импорт

export default function TodoPage() {
  return (
    <main>
      {/* ... форма добавления */}
      <ul>
        {tasks.map(task => (
          <li key={task.id}>
            {task.text}
            {/* Форма удаления */}
            <form action={deleteTask} style={{ display: "inline" }}>
              <input type="hidden" name="id" value={task.id} />
              <button type="submit">✖️</button>
            </form>
            {/* Форма редактирования */}
            <form action={updateTask} style={{ display: "inline" }}>
              <input type="hidden" name="id" value={task.id} />
              <input type="text" name="text" defaultValue={task.text} />
              <button type="submit">Сохранить</button>
            </form>
          </li>
        ))}
      </ul>
    </main>
  );
}

Теперь каждая задача может быть отредактирована через свою форму.

4. Немного про валидацию и обработку ошибок

Server Actions позволяют возвращать значения и обрабатывать ошибки. Например, если пользователь отправил пустую задачу, можно вернуть ошибку и показать её на клиенте.

Пример: возврат ошибки


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

import { tasks } from "../_data/tasks";

export async function addTask(formData) {
  const text = formData.get("text");
  if (!text || text.trim().length < 3) {
    // Можно выбросить ошибку или вернуть объект с ошибкой
    return { error: "Текст задачи должен быть не менее 3 символов" };
  }
  const newTask = { id: Date.now(), text };
  tasks.push(newTask);
  return newTask;
}

На клиенте можно обработать результат Server Action с помощью хуков useFormState или useFormStatus (подробнее — в следующих лекциях).

5. Мутация с асинхронной работой: интеграция с базой данных

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

Пример с "базой данных" (имитация)


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

import { db } from "../_data/db"; // Представим, что это наша база

export async function addTask(formData) {
  const text = formData.get("text");
  if (!text) return;
  // Имитация асинхронного запроса
  const newTask = await db.tasks.create({ text });
  return newTask;
}

В реальном проекте вы бы использовали, например, Prisma или другой ORM для работы с базой.

6. Паттерны: несколько мутаций в одном компоненте

Server Actions можно комбинировать: например, на одной странице можно реализовать добавление, удаление и обновление разных сущностей, передавать разные функции в разные формы.

Пример: список пользователей


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

import { users } from "../_data/users";

export async function addUser(formData) { /* ... */ }
export async function deleteUser(formData) { /* ... */ }
export async function updateUser(formData) { /* ... */ }

// app/page.jsx
import { addUser, deleteUser, updateUser } from "./actions/userActions";
import { users } from "./_data/users";

export default function UserPage() {
  return (
    <main>
      <form action={addUser}>...</form>
      <ul>
        {users.map(u => (
          <li key={u.id}>
            {u.name}
            <form action={deleteUser}>...</form>
            <form action={updateUser}>...</form>
          </li>
        ))}
      </ul>
    </main>
  );
}

7. Особенности работы с Server Actions для мутаций

  • Строго серверный контекст: Server Action всегда выполняется на сервере, никакого доступа к window, document и прочим браузерным API.
  • Безопасность: Не доверяйте данным из формы, всегда проверяйте и валидируйте (особенно если работаете с базой).
  • Мутация данных должна быть атомарной: Не забывайте, что параллельные запросы могут привести к гонкам данных, если не использовать настоящую базу.
  • Работа с состоянием: После мутации страница или компонент может автоматически перерисоваться, если вы используете Server Components. Для оптимистичного UI используйте хуки из следующих лекций.

8. Типичные ошибки при использовании Server Actions для мутаций

Ошибка №1: попытка мутировать данные вне серверного контекста.
Если вы случайно попытаетесь использовать Server Action в Client Component (без "use server"), Next.js ругнётся. Всегда объявляйте Server Action с "use server".

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

Ошибка №3: мутация данных только в памяти.
В демо-примерах часто используют массивы в памяти, но при перезапуске сервера все данные исчезают. В продакшене всегда используйте базу данных!

Ошибка №4: отсутствие валидации данных.
Любые данные, пришедшие с клиента, нужно валидировать на сервере. Не доверяйте данным из формы — даже если пользователь не может изменить hidden-поле, всегда проверяйте их на сервере.

Ошибка №5: неправильная работа с асинхронностью.
Если забыть добавить await при работе с базой, мутация может не завершиться вовремя, и пользователь не увидит результат.

Ошибка №6: дублирование кода Server Actions.
Иногда начинающие создают слишком много однотипных Server Actions для каждой мелочи. Лучше переиспользуйте одну функцию, если логика схожа.

3
Опрос
Введение в Server Actions, 10 уровень, 4 лекция
Недоступен
Введение в Server Actions
Введение в Server Actions
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ