😶🌫️
React × Material-UIでユーザーの入力を待機する確認ダイアログカスタムフックの実装
React と Material-UI(以下MUI)を使った確認ダイアログの実装についてのメモです。
やりたいこと
以下のように処理の途中で確認ダイアログを呼び、ユーザーの入力に応じて処理できるようにするのが目標です。
const onClick = async () => {
const result = await openDialog(); // ここでダイアログを開き、ユーザーの入力を待機
if (result === 'confirm') { // ユーザーがOKした場合
console.log('Confirmed');
} else { // ユーザーがキャンセルした場合
console.log('Canceled');
}
};
1. 確認ダイアログの実装
1.1 確認ダイアログコンポーネントの作成
まず、MUIを利用してシンプルな確認ダイアログコンポーネントを作成しました。
useConfirmDialog.ts
import { Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, Button } from "@mui/material";
import { FC } from "react";
type ConfirmDialogResult = "confirm" | "cancel";
type ConfirmDialogProps = {
open: boolean;
onClose: (result: ConfirmDialogResult) => void;
title: string;
message: string;
};
const _ConfirmDialog: FC<ConfirmDialogProps> = ({ open, onClose, title, message }) => (
<Dialog open={open} onClose={() => onClose("cancel")}>
<DialogTitle>{title}</DialogTitle>
<DialogContent>
<DialogContentText>{message}</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => onClose("cancel")} variant="outlined">
キャンセル
</Button>
<Button onClick={() => onClose("confirm")} autoFocus>
確認
</Button>
</DialogActions>
</Dialog>
);
ユーザーが確認した場合はonClose("confirm")
、キャンセルした場合はonClose("cancel")
を呼びます。
また、ダイアログのタイトルとメッセージをpropsで渡すことができます。
見た目はこんな感じです
1.2 カスタムフックの作成
ここが本題です。以下のように確認ダイアログコンポーネント(ConfirmDialog
)と、ダイアログを開くための関数(openConfirmDialog
)を返すカスタムフックを作成しました。
useConfirmDialog.ts
export const useConfirmDialog = () => {
const [open, setOpen] = useState(false);
const [resolve, setResolve] = useState<(value: ConfirmDialogResult) => void>();
const openConfirmDialog = () => {
setOpen(true);
return new Promise<ConfirmDialogResult>((resolve) => {
setResolve(() => resolve);
});
};
const onClose = (result: ConfirmDialogResult) => {
setOpen(false);
if (resolve) {
resolve(result);
}
};
const ConfirmDialog: FC<{ title: string; message: string }> = (props) => (
<_ConfirmDialog open={open} onClose={onClose} title={props.title} message={props.message} />
);
return {
ConfirmDialog,
openConfirmDialog,
};
};
ポイントとしてはダイアログを開く際に呼ばれるopenConfirmDialog
の中でPromiseを返し、ダイアログを閉じる際に呼ばれるonClose
の中でresolveを呼ぶことでユーザーの確認またはキャンセルの選択後にPromiseが解決されるようになっている点です。
これによってdialogが閉じられた際の結果を返すことができます。
const openConfirmDialog = () => {
setOpen(true);
return new Promise<ConfirmDialogResult>((resolve) => { // Promiseを返す
setResolve(() => resolve); // resolveをstateにset
});
};
const onClose = (result: ConfirmDialogResult) => {
setOpen(false);
if (resolve) {
resolve(result); // 結果をresolveで返す
}
};
2. 使い方
App.tsx
import { FC } from "react";
import { Button } from '@material-ui/core';
import useConfirmDialog from './useConfirmDialog';
const App: FC = () => {
const [ConfirmDialog, openConfirmDialog] = useConfirmDialog();
const onClick = async () => {
const result = await openConfirmDialog();
if (result === "confirm") {
console.log("Confirmed!!");
} else {
console.log("Canceled");
}
};
return (
<>
<Button onClick={onClick}>確認ダイアログを開く</Button>
<ConfirmDialog title="確認ダイアログ" message="本当に実行しますか?" />
</>
);
};
export default App;
openConfirmDialog
でダイアログを開き、ユーザーの入力を待って、その結果に応じて処理を行なっています。やりたかったことが実現できました!
参考
Discussion
こんなやりかたがあるんですね。カリー化で実現するPromise遅延実行みたいなようなものでしょうか。
なかなか思いつかなそうで、勉強になりました。
デモコードです。
デモサイトです。
簡単ですが、以上です。