💬

React ダイアログ(モーダル)コンポーネントでも window.confirm の使い心地を実現

2024/09/24に公開

概要

window.confirm はすごく便利な API です。自動で確認ダイアログを出し、ユーザーが承認またはキャンセルするまで処理を待機し、確定したら結果を教えてくれます。

しかし実際の開発ではデザイナーさんが考えて作ってくれたデザインを実現するために window.confirm は利用せず、自前で確認ダイアログ(モーダル)を用意する、、、なんて場合も多いのではないでしょうか?

本記事では React のダイアログコンポーネントで window.confirm のような使い心地を実現する方法(デモあり)を紹介します。

今回は TODO アプリのタスク削除機能を想定し、タスクの削除時に確認ダイアログを出して OK なら削除し、そうでなければ削除処理を中断します。
window.confirm を利用した例はこんな感じです。

window.confirm を利用した例

TodoApp.tsx
export const TodoApp = () => {
  ...

  // タスク削除処理
  const deleteTodo = async (id: number) => {
    if (!window.confirm(`本当に削除しますか?`)) return; // キャンセルなら処理を中断

    // 削除処理の実行
    ...
  };

  return (
   ...
      {/* タスクリスト */}
      {todoList.map(({ id, name }) => (
        <Flex key={id}>
          <Checkbox label={name} />
          <Button onClick={() => deleteTodo(id)}>
            削除
          </Button>
        </Flex>
      ))}
   ...
  );
};

実現方法

window.confirm のように処理を待機させるために Promise オブジェクトを利用します。
Promise オブジェクトを作成して、ユーザー操作によって解決されるまで待機し、解決された結果によって処理を分岐するという感じです。
実際のコードを見たほうがイメージしやすいかもしれません。

Promise オブジェクトの利用

TodoApp.tsx
export const TodoApp = () => {
  ...

+ // Promise オブジェクトの resolve 関数を state 変数で管理(確認ダイアログの開閉は undefined かどうかで判定)
+ const [resolve, setResolve] = useState<(value: boolean) => void>();
+
+ // Promise オブジェクトを作成して返し、その resolve 関数を state 変数へセット
+ // NOTE: setResolve(resolve) としないのは resolve が関数であるため state の更新関数と扱われうまくいかないため
+ const confirm = () => new Promise<boolean>((resolve) => setResolve(() => resolve));
+
+ const confirmOK = () => {
+   resolve?.(true);
+   setResolve(undefined); // 確認ダイアログを閉じる
+ };
+
+ const confirmNG = () => {
+   resolve?.(false);
+   setResolve(undefined); // 確認ダイアログを閉じる
+ };

  // タスク削除処理
  const deleteTodo = async (id: number) => {
-   if (!window.confirm(`本当に削除しますか?`)) return; // キャンセルなら処理を中断
+   if (!(await confirm())) return;  // キャンセルなら処理を中断

    // 削除処理の実行
    ...
  };

  return (
   ...
      {/* タスクリスト */}
      {todoList.map(({ id, name }) => (
        <Flex key={id}>
          <Checkbox label={name} />
          <Button onClick={() => deleteTodo(id)}>
            削除
          </Button>
        </Flex>
      ))}
   ...
+     {/* 削除確認ダイアログ */}
+     <Modal opened={!!resolve} onClose={confirmNG}>
+       <Text>本当に削除しますか?</Text>
+       <Flex>
+         <Button onClick={confirmNG}>
+           キャンセル
+         </Button>
+         <Button onClick={confirmOK}>
+           OK
+         </Button>
+       </Flex>
+     </Modal>
  );
};

まとめ

いかがだったでしょうか?
Promise オブジェクトの特性をうまく利用して window.confirm の使い心地を実現することができました。
コーディングのヒントになれば幸いです。

こちらのアイデアはすでに他の方も記事にまとめてくださっています。発展的な内容も含まれてるので、ぜひ参考欄の記事も確認してみてください。

参考

https://medium.com/@kch062522/useconfirm-a-custom-react-hook-to-prompt-confirmation-before-action-f4cb746ebd4e

https://zenn.dev/bmth/articles/react-confirm-dialog

Discussion