【React】window.confirmの代替となるPromiseな確認ダイアログを自作する
ブラウザ標準ではなく任意のUIで確認ダイアログを実装したい場面があったので、今回はwindow.confirm()
のようにメインスレッドをブロックするモーダルをReact Hooksを利用して実装してみる。
1. カスタムフックを実装
import { useCallback, useState } from 'react';
type Confirm = { isOpen: boolean; resolve: (() => void) | null; reject: (() => void) | null;};
export default function useConfirm() { const [confirm, setConfirm] = useState<Confirm>({ isOpen: false, resolve: null, reject: null, });
const isConfirmed = useCallback(() => { const promise = new Promise<void>((resolve, reject) => { setConfirm({ isOpen: true, resolve, reject }); }); return promise.then( () => true, () => false, ); }, []);
const reset = useCallback(() => { setConfirm({ isOpen: false, resolve: null, reject: null }); }, []);
return { ...confirm, isConfirmed, reset };}
2. モーダルを実装
import { type ReactElement, type ReactNode, useEffect, useRef } from 'react';
type Params = { children: ReactNode; onClose?: () => void;};
export default function OpenedModal({ children, onClose,}: Params): ReactElement { const dialogRef = useRef<HTMLDialogElement | null>(null);
useEffect(() => { if (!dialogRef.current?.open) dialogRef.current?.showModal(); }, []);
return ( <dialog ref={dialogRef} onClose={onClose}> {children} </dialog> );}
ClickEvent
などをトリガーとして開くイベント駆動のものではなく、最初から開いているモーダルを実装する。
またEsc
キーを押下して閉じされることを考慮してonClose
を引数として受け付けるようにする。
3. ConfirmButtonを実装
'use client';
import { type ReactElement, useCallback } from 'react';import useConfirm from '@/presentation/hooks/use-confirm';import OpenedModal from '@/presentation/components/common/modal/opened-modal';
export default function ConfirmButton(): ReactElement { const { isOpen, resolve, reject, isConfirmed, reset } = useConfirm();
const handleClick = useCallback(async () => { // sample process if (await isConfirmed()) alert('OK'); else alert('Cancel');
reset(); }, [isConfirmed, reset]);
return ( <> <button type="button" onClick={handleClick}> Confirm </button> {isOpen && ( <OpenedModal onClose={reset}> <button type="button" onClick={reject ?? undefined}> Cancel </button> <button type="button" onClick={resolve ?? undefined}> OK </button> </OpenedModal> )} </> );}
先ほど実装したuseConfirm
フックを利用し、モーダル内のCancelボタンをクリックするとreject()
、OKボタンをクリックするとresolve()
を実行する。
そうすることでモーダル内のボタンを押下を待ち、押下後にalert()
が実行されるようになる。
参考