White Box技術部

WEB開発のあれこれ(と何か)

【React】未編集時に確認ダイアログを出すカスタムフック

『Formデータに編集済みデータがあれば、ブラウザの戻るが実行された場合に確認ダイアログを出す』というよくあるやつを出したくなって調べたところ、 この記事にあたりました。

わかりやすくていい記事だったので、やりたい人はこれを見てくださいで良いのですが、私はカスタムフックにして利用しました。

ちなみにnext/navigationの方のRouterだと、beforePopStateが存在しないので、appDirを使っている場合はEventListenerを使いましょう。

カスタムフック

useBackConfirm.ts

import { useState, useEffect } from "react";

const CONFIRM_MESSAGE = "保存されていないデータは削除されますが、よろしいですか?";

// setIsEditedにtrueが設定されていた場合、ページバック時に確認モーダルを表示する
export const useBackConfirm = (message?: string) => {
  const [isEdited, setIsEdited] = useState(false);

  const handlePopstate = () => {
    const ok = confirm(message || CONFIRM_MESSAGE);
    if (ok) {
      setIsEdited(false);
      window.history.back();
    } else {
      history.pushState(null, "", null);
    }
  };

  useEffect(() => {
    if (isEdited) {
      // ダミー履歴を挿入して「戻る」を1回分吸収する
      history.pushState(null, "", null);
      window.addEventListener("popstate", handlePopstate, false);
    }
    return () => {
      window.removeEventListener("popstate", handlePopstate, false);
    };
  }, [isEdited]);

  return { setIsEdited } as const;
};

使い方

react-hook-formで利用する場合は、formState.isDirtyの値をuseEffectで見て、setIsEditedに値を設定すると楽です。

  const { setIsEdited } = useBackConfirm();
  const { register, formState: { isDirty } } = useForm<SampleForm>();

  useEffect(() => {
    if (isDirty) {
      setIsEdited(true);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [isDirty]);

このサンプルコードではダイアログのメッセージを変えたい場合にも対応しましたが、 この手の確認メッセージを一部だけ変えたいというのは実運用だと無いと思うので、パラメータは無い方が利用時に迷わなくて良いかもしれません。