🚸

React Routerで画面遷移前に破棄確認ダイアログを表示する

2024/10/13に公開

本記事で使用しているReact Routerのバージョンは6.27.0です。6.4.0より前のバージョンではこれから紹介するコードは動作しません。

破棄確認ダイアログとは

フォームなどを入力して保存する前にリロードやページ遷移をしようとした時に表示される以下のようなダイアログのことをここでは指します。

Zennのエディター上で文字を入力後にリロードを行い、確認ダイアログを表示しています。

React Routerで破棄確認ダイアログを実装する

import { useCallback, useState } from "react";
import { unstable_usePrompt, useBeforeUnload } from "react-router-dom";

const message = "行った変更が保存されない可能性があります。";

export function Form() {
  const [name, setName] = useState("");
  const isDirty = name !== "";

  // ページリロードやSPAではないページに遷移するときに確認ダイアログを表示する
  useBeforeUnload(
    useCallback(
      (event) => {
        if (!isDirty) {
          return;
        }

        if (window.confirm(message) === false) {
          // Cancel the event as stated by the standard.
          event.preventDefault();
          // Chrome requires returnValue to be set.
          event.returnValue = "";
        }
      },
      [isDirty]
    )
  );

  // SPA遷移するときに確認ダイアログを表示する
  unstable_usePrompt({
    message,
    when: ({ currentLocation, nextLocation }) =>
      isDirty && currentLocation.pathname !== nextLocation.pathname,
  });

  return (
    <form>
      <label>
        Name:
        <input
          onChange={(event) => {
            setName(event.target.value);
          }}
          value={name}
          type="text"
        />
      </label>
      <button type="submit">Submit</button>
    </form>
  );
}

実際の挙動

上記のコードを実際に動かして確認ダイアログが表示されることを確認している

解説

上記のコードでは確認ダイアログを出すために2つのhookを使用しています。1つ目はuseBeforeUnload です。

このhookを使用することで beforeunloadイベントが発生した時の挙動を制御することができます。beforeunloadイベント はページリロードやタブ閉じ、SPAではないページに遷移する時などに発生するイベントのため、ユーザーがそれらの行動を行なった際に確認ダイアログを表示することができます。このhookの内部実装は addEventListenerbeforeunloadイベント を補足しているだけなのでこれを必ず使う必要があるかと言われるとそうではないです。

https://github.com/remix-run/react-router/blob/7607712908f2a1f7e0375e48185dbc9c7171e541/packages/react-router-dom/index.tsx#L1917-L1929

2つ目はunstable_usePromptです。

このhookを使用することでSPA遷移を補足することができ、遷移前に確認ダイアログを表示することができます。SPA遷移は History API を使用しているため、useBeforeUnload ではイベントを補足することができません。そのため、そちらとは別にこちらのhookが必要になります。

落とし穴 🕳️

今回の実装で使用したunstable_usePromptunstable_usePromptの内部で使用されているuseBlockerなどのhookを使用するためには、createBrowserRouterなどの関数でルートを定義する必要があります。昔ながら?のBrowserRouterなどのRouter Componentによってルートを定義している場合は以下のようなエラーが発生するので注意が必要です。

Uncaught Error: useBlocker must be used within a data router.  See https://reactrouter.com/routers/picking-a-router.

ちなみにこのエラー文に記載されているリンクが切れていたので修正のPRを出してみたらマージしてもらえました😄

https://github.com/remix-run/react-router/pull/12110

まとめ

React Routerで破棄確認ダイアログを表示する方法をご紹介しました。個人的に unstable_usePrompt の引数の when の命名がカッコイイなと思ったので、どこかで真似したいです。

Discussion