今回はNext.jsですが、Reactで確認ダイアログを出す時、confirm関数的なものをawaitで呼べたら楽ちんだよなあと思うのでその実装方法を書きます。そのページに別途ダイアログ用のテンプレートは記述しない方法です。
例えば下記のような感じです。
const onClick = useCallback(async () => {
const isConfirmed = await confirm('タイトル', 'OKですか?')
if (!isConfirmed) {
return
}
doSomething()
}, [])
まずはダイアログの状態管理を行います。今回は状態管理としてRecoilを使っています。下記のような useConfirmDialog.ts
を作成します。
import { useCallback } from 'react'
import { atom, useRecoilState } from 'recoil'
type OpenArgs = {
title?: string
body: React.ReactNode
}
type DialogProps = OpenArgs & {
resolve: (value: boolean) => void
}
const dialogPropsState = atom<DialogProps>({
key: 'confirmDialog/dialogProps',
default: undefined,
})
export function useConfirmDialog() {
const [dialogProps, setDialogProps] = useRecoilState(dialogPropsState)
const close = useCallback(
(result: boolean) => {
console.log({ dialogProps })
if (dialogProps.resolve) {
dialogProps.resolve(result)
}
setDialogProps(undefined)
},
[dialogProps]
)
function open({ title, body }: OpenArgs): Promise<boolean> {
return new Promise((resolve) => {
setDialogProps({
title,
body,
resolve,
})
})
}
return { isOpen: !!dialogProps, open, close, dialogProps }
}
ダイアログが開いているかの isOpen
、開くための関数 open
、閉じるための close
、ダイアログの設定である dialogProps
を出力します。
ここで重要なのがPromiseのresolveをdialogPropsとして保存しているところです。これを保存しておき、ダイアログが終わるタイミングでそのresolveを呼び出すことで呼び出し元のawaitを終了させることができます。
ちなみにこのdialogPropsという形ではなく、タイトルや内容、resolveをそれぞれのstateにすることも考えられますが、これはだめでした。なぜかresolveが勝手にundefinedになってしまい即awaitが終了してしまいます。何か勝手に解放されてしまうのでしょうか。ということでここが一番の肝でした。
ダイアログ自体は ConfirmDialogProvider というコンポーネントを作り、アプリケーション全体を囲むレイアウトとして設置しておきます。
Next.jsであれば pages/_app.tsx
に下記のような感じです。
function MyApp({ Component, pageProps }) {
const colors = useColors()
return (
<RecoilRoot>
<ConfirmDialogProvider>
<Component {...pageProps} />
</ConfirmDialogProvider>
</RecoilRoot>
)
}
ConfirmDialogProviderは下記のような感じです。Chakra UIのダイアログを使った例です。
import { useConfirmDialog } from '@/hooks/dialog/useConfirmDialog'
import { useLocale } from '@/hooks/useLocale'
import {
AlertDialog,
AlertDialogOverlay,
AlertDialogContent,
AlertDialogHeader,
AlertDialogBody,
AlertDialogFooter,
Button,
} from '@chakra-ui/react'
import { useRef } from 'react'
type Props = {
children: React.ReactNode
}
export default function ConfirmDialogProvider(props: Props) {
const { isOpen, close, dialogProps } = useConfirmDialog()
const { title, body } = dialogProps ?? {}
const cancelRef = useRef()
return (
<>
{props.children}
<AlertDialog
isOpen={isOpen}
leastDestructiveRef={cancelRef}
onClose={() => close(false)}
>
<AlertDialogOverlay>
<AlertDialogContent>
{title && (
<AlertDialogHeader fontSize="lg" fontWeight="bold">
{title}
</AlertDialogHeader>
)}
<AlertDialogBody mt={title ? undefined : 4}>{body}</AlertDialogBody>
<AlertDialogFooter>
<Button ref={cancelRef} onClick={() => close(false)}>
Cancel
</Button>
<Button colorScheme="teal" onClick={() => close(true)} ml={3}>
OK
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogOverlay>
</AlertDialog>
</>
)
}
これで確認が楽ちんになりました。
Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。
また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!
こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?
コメント