Обработка незавершенных изменений

В 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 и продолжить работу с данными формы.


Ключевые принципы работы с незавершенными изменениями

  • Отслеживание состояния изменений: всегда фиксировать, когда данные формы или других компонентов изменяются.
  • Блокировка навигации: использовать встроенные возможности React Router или собственные хуки для предотвращения случайных переходов.
  • Подтверждение действий: предоставлять пользователю возможность решить, стоит ли покидать страницу.
  • Сохранение данных: использовать локальное хранилище, сессионное хранилище или API для временного сохранения изменений.
  • Совместимость с навигацией браузера: сочетать внутренние блокировки с beforeunload для обработки закрытия вкладки или обновления страницы.

Эти методы обеспечивают надежную обработку незавершенных изменений, повышая удобство и безопасность пользовательского интерфейса в приложениях на React Router.