В React Router предусмотрены механизмы для контроля навигации между маршрутами, включая возможность блокировки переходов при определённых условиях. Это особенно важно в случаях, когда нужно предупредить пользователя о потере несохранённых данных или остановить переход в зависимости от состояния приложения.
usePrompt и
Prompt в React RouterВ версии React Router до v6 использовался компонент
<Prompt> для блокировки навигации. Он принимал два
основных свойства:
when — логическое значение, указывающее, активна ли
блокировка.message — сообщение или функция, возвращающая сообщение
для подтверждения перехода.Пример использования:
import { Prompt } from 'react-router-dom';
import { useState } from 'react';
function Form() {
const [isBlocking, setIsBlocking] = useState(false);
return (
<form
onCha nge={() => setIsBlocking(true)}
onSub mit={() => setIsBlocking(false)}
>
<Prompt
when={isBlocking}
message="Есть несохранённые изменения. Вы уверены, что хотите покинуть страницу?"
/>
<input type="text" placeholder="Введите данные" />
<button type="submit">Сохранить</button>
</form>
);
}
В этом примере навигация будет заблокирована, пока поле формы содержит изменения. Пользователь получит предупреждение при попытке уйти со страницы.
В React Router v6 компонент <Prompt> был удалён.
Для аналогичной функциональности используется хук
useBlocker, который можно реализовать
через useNavigate и useLocation.
Создадим кастомный хук usePrompt на базе нового API:
import { useContext, useEffect } from 'react';
import { UNSAFE_NavigationContext as NavigationContext } from 'react-router-dom';
function useBlocker(blocker, when = true) {
const navigator = useContext(NavigationContext).navigator;
useEffect(() => {
if (!when) return;
const unblock = navigator.block((tx) => {
const autoUnblockingTx = {
...tx,
retry() {
unblock();
tx.retry();
}
};
blocker(autoUnblockingTx);
});
return unblock;
}, [navigator, blocker, when]);
}
export function usePrompt(message, when = true) {
const blocker = (tx) => {
if (window.confirm(message)) {
tx.retry();
}
};
useBlocker(blocker, when);
}
Применение хука в компоненте формы:
import { useState } from 'react';
import { usePrompt } from './usePrompt';
function Form() {
const [isBlocking, setIsBlocking] = useState(false);
usePrompt("Есть несохранённые изменения. Вы уверены, что хотите покинуть страницу?", isBlocking);
return (
<form
onCha nge={() => setIsBlocking(true)}
onSub mit={() => setIsBlocking(false)}
>
<input type="text" placeholder="Введите данные" />
<button type="submit">Сохранить</button>
</form>
);
}
Блокировка навигации работает не только при использовании истории
браузера, но и при переходе через <Link> или
navigate(). Внутри blocker можно вызывать
tx.retry() для подтверждённого перехода или отменять
переход.
const blocker = (tx) => {
if (window.confirm("Вы хотите покинуть страницу с несохранёнными изменениями?")) {
tx.retry(); // продолжает навигацию
}
// иначе переход отменяется автоматически
};
Часто требуется блокировать переходы только при определённых условиях, например, если форма содержит изменения:
const [isFormDirty, setIsFormDirty] = useState(false);
usePrompt(
"Вы уверены, что хотите уйти, не сохранив изменения?",
isFormDirty
);
Блокировка автоматически активируется при изменении состояния
isFormDirty и снимается, если состояние меняется на
false.
usePrompt и useBlocker в React Router v6
не являются официальным публичным API и используют
UNSAFE_NavigationContext. Это значит, что при обновлении
версии библиотеки поведение может измениться.beforeunload:useEffect(() => {
const handleBeforeUnload = (e) => {
if (isFormDirty) {
e.preventDefault();
e.returnValue = '';
}
};
window.addEventListener('beforeunload', handleBeforeUnload);
return () => window.removeEventListener('beforeunload', handleBeforeUnload);
}, [isFormDirty]);
beforeunload и показывают стандартный текст
предупреждения.tx.retry() для повторного выполнения
навигации после подтверждения.Блокировка навигации позволяет создавать безопасный пользовательский опыт, предотвращая потерю данных и обеспечивая контроль над переходами между страницами.