В React Router управление состоянием маршрутизации тесно связано с состоянием приложения. Одной из важных задач является обработка незавершенных изменений, таких как формы с несохраненными данными или действия, которые пользователь начал, но не завершил. Без корректной обработки таких сценариев пользователи могут случайно потерять данные, что ухудшает пользовательский опыт.
React Router предоставляет возможность блокировки
навигации, чтобы предотвратить уход с текущей страницы при
наличии незавершенных изменений. Основной инструмент для этого — хук
usePrompt (до версии 6) или комбинация
useNavigate и пользовательской логики подтверждения в
версиях 6+.
Пример использования логики блокировки в React Router v6:
import { useNavigate, useLocation, UNSAFE_NavigationContext } from "react-router-dom";
import { useContext, useEffect } from "react";
function useBlocker(blocker, when = true) {
const { navigator } = useContext(UNSAFE_NavigationContext);
useEffect(() => {
if (!when) return;
const unblock = navigator.block((tx) => {
const autoUnblockingTx = {
...tx,
retry() {
unblock();
tx.retry();
}
};
blocker(autoUnblockingTx);
});
return unblock;
}, [navigator, blocker, when]);
}
В этом коде useBlocker позволяет блокировать навигацию и
вызывать коллбэк blocker, если переход возможен. Метод
retry() даёт возможность повторить навигацию после
подтверждения пользователем.
Частый сценарий — это формы с изменениями. Необходимо отслеживать состояние формы и вызывать блокировку, если пользователь пытается уйти.
import { useState } from "react";
import { useBlocker } from "./useBlocker";
function FormPage() {
const [isDirty, setIsDirty] = useState(false);
useBlocker((tx) => {
if (window.confirm("Есть несохраненные изменения. Продолжить?")) {
tx.retry();
}
}, isDirty);
return (
<form>
<input type="text" onCha nge={() => setIsDirty(true)} />
<button type="submit">Сохранить</button>
</form>
);
}
В этом примере isDirty отслеживает
состояние формы. Если оно истинно, пользователь получает предупреждение
при попытке навигации.
Иногда требуется выполнять отложенные действия, если переход был заблокирован. Например, можно сохранять данные локально или отправлять их на сервер:
function saveDraft(data) {
localStorage.setItem("draft", JSON.stringify(data));
}
useBlocker((tx) => {
saveDraft(formState);
tx.retry();
}, isDirty);
Таким образом, даже если пользователь покинет страницу, данные не будут потеряны.
useBeforeUnloadДля дополнительной защиты можно использовать нативное событие
beforeunload, чтобы предотвратить потерю данных при
закрытии вкладки:
import { useEffect } from "react";
useEffect(() => {
const handleBeforeUnload = (e) => {
if (isDirty) {
e.preventDefault();
e.returnValue = "";
}
};
window.addEventListener("beforeunload", handleBeforeUnload);
return () => window.removeEventListener("beforeunload", handleBeforeUnload);
}, [isDirty]);
Этот подход работает совместно с блокировкой навигации и обеспечивает сохранность данных при закрытии вкладки или перезагрузке страницы.
В React Router 6+ маршруты могут хранить состояние, что позволяет автоматически переносить информацию о незавершенных изменениях между переходами:
import { useNavigate, useLocation } from "react-router-dom";
const navigate = useNavigate();
const location = useLocation();
const nextPage = "/next";
navigate(nextPage, { state: { formDraft: formState } });
При этом целевая страница может извлечь состояние через
location.state и продолжить работу с данными формы.
beforeunload для обработки закрытия
вкладки или обновления страницы.Эти методы обеспечивают надежную обработку незавершенных изменений, повышая удобство и безопасность пользовательского интерфейса в приложениях на React Router.