Пошук уроків, статей та іншого контенту
Дізнаєтеся, як передавати до Server Action додаткові значення через bind та FormData.
Server Action — це асинхронна функція, яка виконується на сервері. Її можна передати у властивість action HTML-форми:
<form action={serverAction}>
...
</form>FormDataasync function createTask(formData: FormData) {
"use server";
const title = formData.get("title");
}Але іноді Server Action потрібні додаткові значення, яких немає серед полів форми. Наприклад:
ідентифікатор проєкту;
ідентифікатор користувача;
тип операції;
ідентифікатор запису, який потрібно оновити.
Для цього можна використати bind.
bindМетод bind створює нову функцію з наперед переданими аргументами.
Нехай Server Action має такий вигляд:
async function createTask(projectId: string, formData: FormData) {
"use server";
// ...
}Перший аргумент — projectId, а другий — formData.
Щоб передати projectId через форму, створимо прив’язану функцію:
const createTaskForProject = createTask.bind(null, projectId);Тепер під час надсилання форми Next.js викличе функцію приблизно так:
await createTaskForProject(formData);Фактично Server Action отримає аргументи в такому порядку:
projectId, formDataПерший аргумент null у bind відповідає значенню this. Для Server Actions він зазвичай не потрібен, тому передають null.
bind і FormDataНижче наведено сторінку, яка створює завдання для конкретного проєкту.
projectId передається через bind, а title і priority надходять із полів форми через FormData.
// app/projects/page.tsx
async function createTask(projectId: string, formData: FormData) {
"use server";
const title = formData.get("title");
const priority = formData.get("priority");
if (typeof title !== "string" || !title.trim()) {
throw new Error("Назва завдання є обов’язковою");
}
if (typeof priority !== "string") {
throw new Error("Пріоритет завдання не вказано");
}
console.log("Нове завдання:", {
projectId,
title: title.trim(),
priority,
});
// Тут можна зберегти завдання в базу даних.
}
export default function ProjectsPage() {
const projectId = "project-123";
const createTaskForProject = createTask.bind(null, projectId);
return (
<main>
<h1>Завдання проєкту</h1>
<form action={createTaskForProject}>
<div>
<label htmlFor="title">Назва</label>
<input id="title" name="title" type="text" />
</div>
<div>
<label htmlFor="priority">Пріоритет</label>
<select id="priority" name="priority" defaultValue="normal">
<option value="low">Низький</option>
<option value="normal">Звичайний</option>
<option value="high">Високий</option>
</select>
</div>
<button type="submit">Створити завдання</button>
</form>
</main>
);
}У цьому прикладі:
projectId відомий у компоненті сторінки.
createTask.bind(null, projectId) прив’язує його до Server Action.
Поля з атрибутами name="title" і name="priority" автоматично потрапляють у FormData.
Server Action отримує спочатку projectId, а потім formData.
FormDataДля отримання значення використовується метод get:
const title = formData.get("title");Аргументом методу get має бути значення атрибута name відповідного поля:
<input name="title" />Потрібно пам’ятати, що formData.get() може повернути:
рядок;
File;
null, якщо поля з таким ім’ям немає.
Тому перед використанням значення бажано перевірити його тип:
const title = formData.get("title");
if (typeof title !== "string") {
throw new Error("Некоректне значення назви");
}Для звичайних текстових полів результатом буде рядок. Для поля завантаження файлу результатом може бути об’єкт File.
bindЧерез bind можна передати кілька значень. Усі вони будуть розташовані перед FormData.
async function updateTask(
projectId: string,
taskId: string,
formData: FormData
) {
"use server";
const title = formData.get("title");
console.log({
projectId,
taskId,
title,
});
}
const updateTaskForItem = updateTask.bind(
null,
"project-123",
"task-456"
);Після надсилання форми Next.js викличе action із такими аргументами:
"project-123", "task-456", formDataФорма використовує вже підготовлену функцію:
<form action={updateTaskForItem}>
<input name="title" />
<button type="submit">Зберегти</button>
</form>bind або приховане поле формиДодаткове значення можна було б передати через приховане поле:
<form action={createTask}>
<input type="hidden" name="projectId" value="project-123" />
<input name="title" />
<button type="submit">Створити</button>
</form>Тоді значення потрібно отримувати з FormData:
async function createTask(formData: FormData) {
"use server";
const projectId = formData.get("projectId");
const title = formData.get("title");
}Однак значення прихованого поля можна змінити в браузері. Тому не слід вважати його надійним або пов’язаним із правами доступу.
bind зручний, коли потрібно передати додаткове значення з серверного компонента:
const createTaskForProject = createTask.bind(null, projectId);Це робить призначення аргументів зрозумілішим і не додає їх до видимих полів форми.
Водночас будь-яке значення, отримане в Server Action, потрібно перевіряти. bind не замінює автентифікацію та перевірку дозволів.
Якщо Server Action використовує bind, додаткові аргументи мають бути оголошені перед FormData:
async function saveComment(
articleId: string,
authorId: string,
formData: FormData
) {
"use server";
const comment = formData.get("comment");
}Правильне створення прив’язаної функції:
const saveCommentForArticle = saveComment.bind(
null,
"article-123",
"user-456"
);Неправильний порядок може призвести до помилок:
// Неправильно для action із bind:
async function saveComment(
formData: FormData,
articleId: string
) {
"use server";
}Після застосування bind перший переданий аргумент потрапить у formData, тому Server Action працюватиме некоректно.
nameЗначення поля потрапляє у FormData лише тоді, коли поле має атрибут name:
<input name="title" />Це поле без name не буде доступним через formData.get():
<input id="title" />FormData.get() може повернути null, тому не варто одразу викликати методи рядка:
const title = formData.get("title");
// Потенційна помилка, якщо title дорівнює null
title.trim();Безпечніший варіант:
const title = formData.get("title");
if (typeof title !== "string" || !title.trim()) {
throw new Error("Потрібно вказати назву");
}FormData стоїть не останнімДля Server Action із додатковими аргументами FormData має бути останнім параметром:
async function createTask(projectId: string, formData: FormData) {
"use server";
}formУ властивість action потрібно передати функцію, а не результат її виклику:
<form action={createTaskForProject}>Неправильно:
<form action={createTaskForProject()}>У другому варіанті функція викликається під час рендерингу, а не після надсилання форми.
Значення з форми, зокрема приховані поля, можуть бути змінені користувачем. На сервері потрібно перевіряти:
чи існує відповідний запис;
чи має поточний користувач доступ до нього;
чи відповідають дані очікуваному формату.
Поля HTML-форми передаються у Server Action через FormData.
Значення поля отримують методом formData.get("ім’я-поля").
Додаткові аргументи можна передати через bind.
Додаткові аргументи оголошуються перед FormData.
Для створення прив’язаної функції використовується конструкція action.bind(null, value).
Поля форми повинні мати атрибут name.
Дані, отримані на сервері, потрібно перевіряти незалежно від способу їх передачі.