Валидация и обработка формы на сервере при помощи React-хука useActionState
Обработка формы почти всегда выполняется на сервере, однако валидация формы чаще всего выполняется на клиенте (в браузере). Но иногда требуется выполнять валидацию и обработку формы на стороне сервера. Это необходимо при регистрации, авторизации и других операциях с повышенными требованиями к безопасности. Кроме того, валидация формы на стороне сервера позволяет реализовать собственную уникальную защиту от спама, не зависящую от внешних сервисов.
В этой статье я покажу простейший пример валидации и обработки формы на стороне сервера при помощи React-хука useActionState.
Решение
1. Создаём функцию serverAction, которая будет выполняться на стороне сервера и осуществлять валидацию и обработку данных, отправляемых из формы:
"use server";
import { z } from "zod";
const formSchema = z.object({
firstname: z.string().min(1),
lastname: z.string().min(1),
email: z.email(),
phone: z
.string()
.transform((val) => val.replace(/[^\d+]/g, ""))
.refine((val) => /^\+?[1-9]\d{9,14}$/.test(val)),
});
export const serverAction = async (prevState, formData) => {
const data = {
firstname: formData.get("firstname"),
lastname: formData.get("lastname"),
email: formData.get("email"),
phone: formData.get("phone"),
};
const validate = formSchema.safeParse(data);
if (validate.success) {
const result = await formHandler(data);
if (result) {
return {
validate: {},
success: true,
error: false,
};
} else {
return {
validate: {},
success: false,
error: true,
};
}
} else {
return {
validate: z.treeifyError(validate.error).properties,
success: false,
error: false,
};
}
};
const formHandler = async (data) => {
// здесь должен быть обработчик формы
await new Promise((resolve) => setTimeout(resolve, 1000));
return true; // или false
};2. Создаём компонент формы, использующий React-хук useActionState для отправки данных на сервер для последующей валидации и обработки:
"use client";
import clsx from "clsx";
import { useState, useEffect, useActionState } from "react";
import { serverAction } from "./actions";
const initFields = {
firstname: "",
lastname: "",
email: "",
phone: "",
};
const initState = {
validate: {},
success: false,
error: false,
};
export default function Form() {
const [state, formAction, isPending] = useActionState(
serverAction,
initState,
);
const [fields, setFields] = useState(initFields);
const [validate, setValidate] = useState({});
const changeFileld = (field, value) => {
setFields((prev) => ({ ...prev, [field]: value }));
setValidate((prev) => ({ ...prev, [field]: undefined }));
};
useEffect(() => {
setValidate(state.validate);
if (state.success) {
setFields(initFields);
console.log("Форма успешно отправлена");
} else if (state.error) {
console.log("Ошибка при отправке формы");
}
}, [state]);
return (
<form action={formAction} noValidate>
<div>
<input
type="text"
name="firstname"
value={fields.firstname}
onChange={(e) => changeFileld("firstname", e.target.value)}
className={clsx({ "border-red-500": validate?.firstname })}
placeholder="Имя"
/>
<div
className={clsx("text-red-500", { invisible: !validate?.firstname })}>
Укажите своё имя
</div>
</div>
<div>
<input
type="text"
name="lastname"
value={fields.lastname}
onChange={(e) => changeFileld("lastname", e.target.value)}
className={clsx({ "border-red-500": validate?.lastname })}
placeholder="Фамилия"
/>
<div
className={clsx("text-red-500", { invisible: !validate?.lastname })}>
Укажите свою фамилию
</div>
</div>
<div>
<input
type="email"
name="email"
value={fields.email}
onChange={(e) => changeFileld("email", e.target.value)}
className={clsx({ "border-red-500": validate?.email })}
placeholder="Email"
/>
<div className={clsx("text-red-500", { invisible: !validate?.email })}>
Введите корректный Email-адрес
</div>
</div>
<div>
<input
type="tel"
name="phone"
value={fields.phone}
onChange={(e) => changeFileld("phone", e.target.value)}
className={clsx({ "border-red-500": validate?.phone })}
placeholder="Телефон"
/>
<div className={clsx("text-red-500", { invisible: !validate?.phone })}>
Введите корректный номер телефона
</div>
</div>
<div>
{isPending ? (
<button type="button">Отправляется</button>
) : (
<button type="submit">Отправить</button>
)}
</div>
</form>
);
}Пример является нестилизованным, за исключением нескольких классов Tailwind, необходимых для выделения ошибок валидации красной рамкой у поля и красной подсказкой под полем.






