【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()が実行されるようになる。

1

参考
  1. A Replacement for window.confirm Using Promises and React Hooks | by Jared | Medium