Пошук уроків, статей та іншого контенту
Керуватимете станом відповіді Server Action, помилками й повідомленнями форми за допомогою useActionState.
useActionStateuseActionState пов’язує Server Action зі станом клієнтського компонента. Хук допомагає:
отримати результат виконання Server Action;
показати повідомлення про успіх або помилку;
відобразити помилки валідації форми;
дізнатися, чи виконується дія через isPending.
Хук повертає три значення:
const [state, formAction, isPending] = useActionState(
serverAction,
initialState
);state — останній стан, повернутий Server Action;
formAction — функція, яку потрібно передати у властивість action форми;
isPending — true, поки Server Action виконується.
useActionState доступний у React 19 і використовується в клієнтських компонентах Next.js.
Без useActionState Server Action форми зазвичай отримує тільки FormData:
async function action(formData) {
"use server";
// ...
}Після підключення useActionState першим аргументом стає попередній стан:
async function action(previousState, formData) {
"use server";
// ...
}Тому порядок аргументів важливий:
previousState — попередній стан;
formData — дані форми.
Server Action має повертати новий стан. Цей результат буде доступний у клієнтському компоненті через state.
Створимо форму з полями електронної пошти й повідомлення. Server Action перевірить дані та поверне або помилки, або повідомлення про успішне надсилання.
Структура файлів:
app/
└── contact/
├── actions.ts
├── contact-form.tsx
└── page.tsxФайл app/contact/actions.ts:
"use server";
export type FormState = {
message: string;
errors?: {
email?: string;
text?: string;
};
};
export async function sendMessage(
previousState: FormState,
formData: FormData
): Promise<FormState> {
const emailValue = formData.get("email");
const textValue = formData.get("text");
const email = typeof emailValue === "string" ? emailValue.trim() : "";
const text = typeof textValue === "string" ? textValue.trim() : "";
const errors: FormState["errors"] = {};
if (!email) {
errors.email = "Введіть електронну пошту.";
} else if (!email.includes("@")) {
errors.email = "Введіть коректну електронну пошту.";
}
if (!text) {
errors.text = "Введіть повідомлення.";
} else if (text.length < 10) {
errors.text = "Повідомлення має містити щонайменше 10 символів.";
}
if (Object.keys(errors).length > 0) {
return {
message: "Перевірте дані форми.",
errors,
};
}
// Тут могла б бути операція з базою даних або надсилання електронного листа.
console.log("Нове повідомлення:", { email, text });
return {
message: "Повідомлення успішно надіслано.",
};
}Параметр previousState у цьому прикладі не використовується, але він має залишатися першим параметром. Це обов’язкова форма функції, яку передають у useActionState.
Стан містить:
message — загальне повідомлення для користувача;
errors.email — помилку поля електронної пошти;
errors.text — помилку поля повідомлення.
Стан має бути серіалізованим: містити рядки, числа, масиви або прості об’єкти. Не слід повертати з Server Action екземпляри класів, функції чи інші несеріалізовані значення.
Файл app/contact/contact-form.tsx:
"use client";
import { useActionState } from "react";
import { sendMessage, type FormState } from "./actions";
const initialState: FormState = {
message: "",
};
export default function ContactForm() {
const [state, formAction, isPending] = useActionState(
sendMessage,
initialState
);
return (
<form action={formAction}>
<div>
<label htmlFor="email">Електронна пошта</label>
<input
id="email"
name="email"
type="email"
aria-invalid={Boolean(state.errors?.email)}
aria-describedby={state.errors?.email ? "email-error" : undefined}
disabled={isPending}
/>
{state.errors?.email && (
<p id="email-error" role="alert">
{state.errors.email}
</p>
)}
</div>
<div>
<label htmlFor="text">Повідомлення</label>
<textarea
id="text"
name="text"
rows={5}
aria-invalid={Boolean(state.errors?.text)}
aria-describedby={state.errors?.text ? "text-error" : undefined}
disabled={isPending}
/>
{state.errors?.text && (
<p id="text-error" role="alert">
{state.errors.text}
</p>
)}
</div>
<button type="submit" disabled={isPending}>
{isPending ? "Надсилання..." : "Надіслати"}
</button>
{state.message && (
<p aria-live="polite">
{state.message}
</p>
)}
</form>
);
}Ключовий рядок:
<form action={formAction}>Форма викликає не саму sendMessage, а функцію formAction, яку повернув useActionState. React автоматично:
збирає дані форми;
передає їх у Server Action;
отримує повернений стан;
оновлює state;
повторно рендерить компонент.
isPending дає змогу заблокувати поля та кнопку на час виконання дії. Це запобігає повторному надсиланню форми.
Файл app/contact/page.tsx:
import ContactForm from "./contact-form";
export default function ContactPage() {
return (
<main>
<h1>Зворотний зв’язок</h1>
<ContactForm />
</main>
);
}page.tsx може залишатися Server Component. Клієнтським є лише компонент, який використовує useActionState.
Коли користувач надсилає порожню форму, Server Action повертає:
{
message: "Перевірте дані форми.",
errors: {
email: "Введіть електронну пошту.",
text: "Введіть повідомлення."
}
}Ці дані стають значенням state у клієнтському компоненті:
state.message
state.errors?.email
state.errors?.textЯкщо дані коректні, Server Action повертає:
{
message: "Повідомлення успішно надіслано."
}Оскільки властивість errors у цьому об’єкті відсутня, попередні помилки більше не відображаються.
Попередній стан можна використовувати, якщо результат дії залежить від попереднього результату. Наприклад, можна зберігати кількість спроб або додаткове повідомлення.
"use server";
type FormState = {
attempts: number;
message: string;
};
export async function checkCode(
previousState: FormState,
formData: FormData
): Promise<FormState> {
const code = formData.get("code");
if (code !== "1234") {
return {
attempts: previousState.attempts + 1,
message: "Неправильний код.",
};
}
return {
attempts: previousState.attempts,
message: "Код правильний.",
};
}Після кожного виклику Server Action React передає повернений раніше стан як previousState під час наступного виклику.
У більшості форм попередній стан потрібен лише для дотримання сигнатури функції. У такому разі його можна не використовувати.
Зручно розділяти два типи результатів:
помилки валідації — очікуваний результат, який повертається у стані;
несподівані помилки сервера — помилки інфраструктури або програмні помилки.
Помилки валідації потрібно повертати явно:
return {
message: "Виправте помилки у формі.",
errors: {
email: "Електронна пошта вже використовується.",
},
};Тоді компонент може показати зрозуміле повідомлення користувачу.
Не варто використовувати throw для звичайної перевірки полів. Якщо користувач не заповнив поле або ввів некоректне значення, це нормальна ситуація, яку краще представити частиною стану.
Неправильно:
export async function sendMessage(
formData: FormData,
previousState: FormState
) {
"use server";
}Після підключення до useActionState правильно:
export async function sendMessage(
previousState: FormState,
formData: FormData
) {
"use server";
}Якщо поміняти параметри місцями, formData не буде об’єктом FormData.
Неправильно:
const [state, formAction] = useActionState(sendMessage, initialState);
<form action={sendMessage}>Потрібно передавати функцію, яку повернув хук:
<form action={formAction}>Саме formAction враховує попередній стан і передає його до Server Action.
"use client"Компонент, у якому викликається useActionState, має бути клієнтським:
"use client";Без цієї директиви використання клієнтського хука спричинить помилку під час збірки.
nameFormData містить лише поля, які мають атрибут name:
<input name="email" />
<textarea name="text" />Без name виклик formData.get("email") поверне null.
Server Action не повинна повертати функції або складні об’єкти:
// Неправильно
return {
message: new Error("Помилка"),
};Повертайте прості значення:
return {
message: "Сталася помилка.",
};useActionState керує результатом Server Action у клієнтському компоненті.
Хук повертає state, formAction та isPending.
Server Action, підключена до useActionState, отримує previousState першим аргументом, а FormData — другим.
Помилки валідації зручно повертати як частину стану.
formAction потрібно передавати у властивість action форми.
isPending допомагає показати стан виконання та заблокувати повторне надсилання.
Стан Server Action має містити серіалізовані значення.