React Router начиная с версии 6.4 внедрил концепцию data
routers, где маршруты могут содержать асинхронные функции для
загрузки данных (loader) и обработки действий пользователей
(action). Механизм action предназначен для
обработки отправки форм и выполнения связанных операций
на стороне маршрута, таких как сохранение данных или их валидация.
Каждый маршрут, который должен обрабатывать отправку формы, может
иметь свойство action, которое принимает функцию:
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import FormPage from "./FormPage";
import { handleFormSubmit } from "./actions";
const router = createBrowserRouter([
{
path: "/form",
element: <FormPage />,
action: handleFormSubmit,
},
]);
function App() {
return <RouterProvider router={router} />;
}
В данном примере при отправке формы с маршрута /form
будет вызвана функция handleFormSubmit.
Функция action принимает объект
ActionFunctionArgs, который содержит:
request — объект запроса, предоставляющий метод
formData(), json(), text() для
чтения данных;params — параметры маршрута, если путь содержит
динамические сегменты;context — опциональный объект контекста, переданный
через роутер.Пример функции action:
export async function handleFormSubmit({ request, params }) {
const formData = await request.formData();
const name = formData.get("name");
const email = formData.get("email");
if (!name || !email) {
return { error: "Все поля обязательны" };
}
// Симуляция отправки данных на сервер
await fetch("/api/submit", {
method: "POST",
body: JSON.stringify({ name, email }),
headers: {
"Content-Type": "application/json",
},
});
return { success: true };
}
Form компонента из React RouterReact Router предоставляет компонент <Form>,
который автоматически связывается с action маршрута и
отправляет данные в соответствующую функцию:
import { Form, useActionData } from "react-router-dom";
export default function FormPage() {
const actionData = useActionData();
return (
<div>
<h2>Регистрация</h2>
{actionData?.error && <p style={{ color: "red" }}>{actionData.error}</p>}
{actionData?.success && <p style={{ color: "green" }}>Данные отправлены!</p>}
<Form method="post">
<label>
Имя:
<input type="text" name="name" />
</label>
<br />
<label>
Email:
<input type="email" name="email" />
</label>
<br />
<button type="submit">Отправить</button>
</Form>
</div>
);
}
Ключевые моменты использования
<Form>:
method может быть "post" или
"get".action текущего
маршрута.useActionData после обработки.Функция action может возвращать данные, которые затем
становятся доступными через useActionData. Это позволяет
реализовать встроенную валидацию форм без
дополнительного состояния:
export async function handleFormSubmit({ request }) {
const formData = await request.formData();
const password = formData.get("password");
if (!password || password.length < 6) {
return { error: "Пароль должен быть не менее 6 символов" };
}
return { success: true };
}
В компоненте формы это можно отобразить сразу после отправки:
{actionData?.error && <p className="error">{actionData.error}</p>}
Для перенаправления после успешной отправки используется функция
redirect из react-router-dom:
import { redirect } from "react-router-dom";
export async function handleFormSubmit({ request }) {
const formData = await request.formData();
const username = formData.get("username");
if (!username) {
return { error: "Введите имя пользователя" };
}
// Сохранение данных
await fetch("/api/register", {
method: "POST",
body: JSON.stringify({ username }),
headers: { "Content-Type": "application/json" },
});
return redirect("/success");
}
При этом <Form> автоматически перенаправит
пользователя на указанный маршрут, если action возвращает
результат redirect.
Для работы с состояниями загрузки после отправки формы используется
хук useNavigation:
import { Form, useNavigation } from "react-router-dom";
export default function FormPage() {
const navigation = useNavigation();
const isSubmitting = navigation.state === "submitting";
return (
<Form method="post">
<input type="text" name="name" />
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? "Отправка..." : "Отправить"}
</button>
</Form>
);
}
Таким образом, форма динамически реагирует на процесс отправки без
дополнительной настройки состояния через useState.
Если маршрут содержит динамические параметры, они доступны в
params функции action:
const router = createBrowserRouter([
{
path: "/users/:userId/edit",
element: <UserEdit />,
action: updateUser,
},
]);
export async function updateUser({ request, params }) {
const formData = await request.formData();
const userId = params.userId;
await fetch(`/api/users/${userId}`, {
method: "PUT",
body: JSON.stringify(Object.fromEntries(formData)),
headers: { "Content-Type": "application/json" },
});
return redirect(`/users/${userId}`);
}
await request.formData() для чтения
данных форм.redirect.<Form> автоматически синхронизируется с
action, не требуется ручная отправка через
fetch.params.Эта модель делает маршруты React Router самодостаточными: каждый маршрут может самостоятельно обрабатывать отправку форм, валидацию и редиректы, уменьшая количество логики в компонентах.