React Router предоставляет мощный инструмент для организации
маршрутизации в приложениях на React, и начиная с версии 6.4 появилась
поддержка Data Router API, где ключевым элементом стали
loader и action. В этой части рассматривается
обработка ошибок в action — функции, которая выполняется
при отправке формы и отвечает за модификацию данных на сервере или их
обработку на клиенте.
actionaction вызывается, когда форма, связанная с маршрутом,
отправляется. Он получает объект request, содержащий все
данные запроса, и объект params, где находятся параметры
маршрута.
Простейший пример action:
export async function action({ request, params }) {
const formData = await request.formData();
const username = formData.get("username");
if (!username) {
throw new Response("Имя пользователя обязательно", { status: 400 });
}
// логика сохранения данных
return redirect(`/profile/${username}`);
}
Ключевой момент: для обработки ошибок используется выброс
экземпляров Response с соответствующим статусом.
Это позволяет маршрутам с ErrorBoundary корректно
отлавливать ошибки.
ErrorBoundary для маршрутовКаждый маршрут в React Router может иметь собственный
errorElement. Если action выбрасывает
исключение или Response с кодом ошибки, компонент
errorElement рендерится вместо обычного компонента
маршрута.
<Route
path="profile"
element={<Profile />}
action={profileAction}
errorElement={<ProfileError />}
/>
Внутри ProfileError можно получить объект ошибки через
хук useRouteError:
import { useRouteError } from "react-router-dom";
function ProfileError() {
const error = useRouteError();
return (
<div>
<h2>Произошла ошибка</h2>
<p>{error.statusText || error.message}</p>
</div>
);
}
Важные моменты:
error.status и error.statusText доступны
только если ошибка была выброшена как Response.throw new Error("...")) можно
использовать error.message.action часто выполняет асинхронные операции: запросы к
API, запись в базу данных, проверки. Для безопасной обработки ошибок
используется блок try/catch:
export async function action({ request }) {
try {
const formData = await request.formData();
const email = formData.get("email");
const response = await fetch("/api/subscribe", {
method: "POST",
body: JSON.stringify({ email }),
});
if (!response.ok) {
throw new Response("Ошибка сервера при подписке", { status: response.status });
}
return redirect("/thank-you");
} catch (err) {
if (err instanceof Response) {
throw err; // пробрасываем Response для ErrorBoundary
}
throw new Response("Непредвиденная ошибка", { status: 500 });
}
}
Такой подход гарантирует, что все ошибки попадут в
ErrorBoundary, а пользователь увидит понятное
сообщение.
React Router не ограничивается простым выбросом ошибок — он поддерживает возврат ошибок в форме без редиректа:
export async function action({ request }) {
const formData = await request.formData();
const password = formData.get("password");
if (password.length < 6) {
return { errors: { password: "Пароль должен быть не менее 6 символов" } };
}
return redirect("/dashboard");
}
В компоненте формы можно использовать useActionData для
отображения ошибок:
import { useActionData } from "react-router-dom";
function LoginForm() {
const actionData = useActionData();
return (
<form method="post">
<input type="password" name="password" />
{actionData?.errors?.password && <p>{actionData.errors.password}</p>}
<button type="submit">Войти</button>
</form>
);
}
Ключевые моменты:
return { errors: {...} } не вызывает
ErrorBoundary.Response для системных
ошибок: сервер недоступен, база данных не отвечает, ошибка
авторизации.errors для ошибок
формы: поля не заполнены, некорректные данные.action
не должна одновременно обрабатывать системные ошибки и ошибки валидации
— лучше делегировать в отдельные утилиты.Response или
объект с errors.action должны
обрабатываться через try/catch, чтобы не возникали
необработанные промисы.action лучше
использовать типовую структуру возврата ошибок, чтобы унифицировать
обработку на фронтенде.action с разными типами ошибокexport async function action({ request }) {
const formData = await request.formData();
const email = formData.get("email");
const password = formData.get("password");
const errors = {};
if (!email.includes("@")) errors.email = "Неверный формат email";
if (password.length < 6) errors.password = "Пароль слишком короткий";
if (Object.keys(errors).length > 0) return { errors };
try {
const response = await fetch("/api/register", {
method: "POST",
body: JSON.stringify({ email, password }),
});
if (!response.ok) throw new Response("Ошибка сервера", { status: response.status });
return redirect("/welcome");
} catch (err) {
if (err instanceof Response) throw err;
throw new Response("Неизвестная ошибка", { status: 500 });
}
}
Такой подход позволяет одновременно обрабатывать:
ErrorBoundary,Хотите, я могу подготовить отдельную схему с визуальным
потоком данных ошибок в action, чтобы её можно
было вставить прямо в учебник и наглядно показать взаимодействие
action, useActionData и
ErrorBoundary?