0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

next.jsのpage routerで未保存確認処理

Posted at

概要

入力項目がいっぱいある画面でブラウザバックや不意に画面を閉じる場合、入力内容が保存されない場合は、確認せず閉じてしまうと、UX的にまずい場面が多い。

途中の入力が失わないように確認モーダルを表示した方が親切に思われる。

ただ、next.jsのpage routerではそいう機能がないので、自力で実装する

詳細

概ね、RouteLeaveHook登録処理と画面遷移時のチェック処理との2つの部分がある

1.RouteLeaveHook登録処理

  • beforeUnloadHandler
  const handleBeforeUnload = (e: BeforeUnloadEvent) => {
    if (condition) {
      // 仕様標準のメソッドを呼び出す
      e.preventDefault();
      // 離脱時に表示するメッセージを設定
      e.returnValue = '保存されてませんが、本当に閉じますか?';
    }
  };

  // didMount時に登録
  window.addEventListener('beforeunload', handleBeforeUnload);
  // willUnmount時に解除
  window.removeEventListener('beforeunload', handleBeforeUnload);

2.RouteLeaveHook

各Reactコンポーネントで

  • RouteLeaveHookのコールバックをSetに登録
  • beforePopStateとhandleRouteStartでSet中のコールバックで確認
const routeLeaveCallback = (url: string, action: LocationChangeAction) => {
  return routeLeaveCallbackImtOrderedSet.every((callback) => {
    return callback(url, action);
  });
};

  const currentLocationRef = useRef<string>('');
  const currentQueryParamRef = useRef<Record<string, string>>({});
  previousLocationImtMap = useSelector((state) => state.app.getPreviousLocationMap());

  useEffect(() => {
    router.beforePopState(({ as }) => {
      if (currentLocationRef.current !== as && !routeLeaveCallback(as, 'POP')) {
        const historyState = window.history.state as NextHistoryState;
        const isBackClicked =
          historyState?.as === previousLocationImtMap?.get('asPath') &&
          historyState?.key === previousLocationImtMap?.get('key');
        window.history.go(isBackClicked ? 1 : -1);
        // 遷移しない
        return false;
      }

      historyAction = 'POP';
      // 遷移する
      return true;
    });

    const handleRouteStart = (asPath: string) => {
      if (currentLocationRef.current !== asPath && !routeLeaveCallback(asPath, historyAction)) {
        router.events.emit('routeChangeError');
        throw 'Abort route change. Please ignore this error.';
      }
    };
    ...

解説

  • handleRouteStartは通常の画面遷移時にrouteLeaveCallbackで確認して、遷移してはいけない場合、routeChangeErrorイベントを発生させて、異常をスローすると、画面遷移が止まる

  • beforePopStateはブラウザのForward/backボタンをクリックした時に呼ばれる

    • そこでrouteLeaveCallbackを呼出して確認して遷移してはいけない場合
      • return falseすることで遷移が止まる
      • ブラウザのアドレスが既に変わってしまったので、window.history.goで逆操作により元に取り戻す
        • 公開apiではないが、next.jsのpage routerにはwindow.history.stateに値が設定されている
          • asはurl、keyは一意的なURLのhistoryキー
          • 覚えた前画面のasとkeyが一致であれば、戻ると、そうでなれば、進むと判断する
          • 戻るがクリックされた場合は、go(1)で、進むがクリックされた場合は、go(-1)でブラウザの URLを取り戻す
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?