Next.js中Shadcn AlertDialog Cancel按钮onClick无法关闭的问题
开发Next.js应用时,在DropdownMenu的菜单项中嵌套AlertDialog组件,发现通过setIsDialogOpen(false)关闭对话框时,只有在调用前有同步/异步操作(比如确认按钮的示例)才生效,直接在取消按钮的onClick事件中调用则无法关闭对话框。
复现代码
"use client"; import { useState } from "react"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/alert-dialog"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Loader2 } from "lucide-react"; export default function Home() { const [isDialogOpen, setIsDialogOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); const handleCancelDelete = () => { setIsDialogOpen(false); }; const handleDelete = async () => { setIsLoading(true); // 模拟异步删除操作 await new Promise((resolve) => setTimeout(resolve, 2000)); setIsLoading(false); }; return ( <div> <DropdownMenu> <DropdownMenuTrigger>Dropdown</DropdownMenuTrigger> <DropdownMenuContent> <DropdownMenuItem>Action</DropdownMenuItem> <DropdownMenuItem onClick={() => setIsDialogOpen(true)} onSelect={(event) => event.preventDefault()} > <AlertDialog open={isDialogOpen} onOpenChange={setIsDialogOpen}> <AlertDialogTrigger>Delete</AlertDialogTrigger> <AlertDialogContent> <AlertDialogHeader> <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle> </AlertDialogHeader> <AlertDialogFooter> <AlertDialogCancel onClick={() => { alert("onClick works"); setIsDialogOpen(false); }} > Cancel </AlertDialogCancel> {isLoading ? ( <Loader2 className="animate-spin" /> ) : ( <AlertDialogAction onClick={async (e) => { e.preventDefault(); await handleDelete(); setIsDialogOpen(false); }} > Continue </AlertDialogAction> )} </AlertDialogFooter> </AlertDialogContent> </AlertDialog> </DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> </div> ); }
问题原因
- 双重触发逻辑冲突:在
DropdownMenuItem上绑定了onClick来打开对话框,同时内部又放置了AlertDialogTrigger,导致AlertDialog的内部状态和外部的isDialogOpen状态不一致。 - 事件冒泡与状态同步问题:AlertDialogCancel的默认关闭逻辑和外部状态更新发生冲突,直接调用
setIsDialogOpen(false)时,DropdownMenu的事件冒泡会干扰AlertDialog的状态同步;而确认按钮中先执行异步操作再更新状态,相当于给了状态同步的缓冲时间,因此能正常关闭。
解决方案
方案1:移除外部手动打开逻辑,依赖AlertDialogTrigger原生行为
直接让AlertDialogTrigger作为DropdownMenuItem的内容,不需要手动控制isDialogOpen,利用AlertDialog的内部状态管理打开/关闭:
"use client"; import { useState } from "react"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/alert-dialog"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Loader2 } from "lucide-react"; export default function Home() { const [isLoading, setIsLoading] = useState(false); const handleDelete = async () => { setIsLoading(true); await new Promise((resolve) => setTimeout(resolve, 2000)); setIsLoading(false); }; return ( <div> <DropdownMenu> <DropdownMenuTrigger>Dropdown</DropdownMenuTrigger> <DropdownMenuContent> <DropdownMenuItem>Action</DropdownMenuItem> <DropdownMenuItem onSelect={(event) => event.preventDefault()}> <AlertDialog> <AlertDialogTrigger>Delete</AlertDialogTrigger> <AlertDialogContent> <AlertDialogHeader> <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle> </AlertDialogHeader> <AlertDialogFooter> <AlertDialogCancel>Cancel</AlertDialogCancel> {isLoading ? ( <Loader2 className="animate-spin" /> ) : ( <AlertDialogAction onClick={handleDelete}> Continue </AlertDialogAction> )} </AlertDialogFooter> </AlertDialogContent> </AlertDialog> </DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> </div> ); }
方案2:保留手动状态控制,修正触发逻辑
如果需要手动控制对话框状态,移除DropdownMenuItem上的onClick,让AlertDialogTrigger负责打开对话框,确保open和onOpenChange正确绑定:
"use client"; import { useState } from "react"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/alert-dialog"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Loader2 } from "lucide-react"; export default function Home() { const [isDialogOpen, setIsDialogOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); const handleDelete = async () => { setIsLoading(true); await new Promise((resolve) => setTimeout(resolve, 2000)); setIsLoading(false); setIsDialogOpen(false); }; return ( <div> <DropdownMenu> <DropdownMenuTrigger>Dropdown</DropdownMenuTrigger> <DropdownMenuContent> <DropdownMenuItem>Action</DropdownMenuItem> <DropdownMenuItem onSelect={(event) => event.preventDefault()}> <AlertDialog open={isDialogOpen} onOpenChange={setIsDialogOpen}> <AlertDialogTrigger>Delete</AlertDialogTrigger> <AlertDialogContent> <AlertDialogHeader> <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle> </AlertDialogHeader> <AlertDialogFooter> <AlertDialogCancel onClick={() => setIsDialogOpen(false)}> Cancel </AlertDialogCancel> {isLoading ? ( <Loader2 className="animate-spin" /> ) : ( <AlertDialogAction onClick={handleDelete}> Continue </AlertDialogAction> )} </AlertDialogFooter> </AlertDialogContent> </AlertDialog> </DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> </div> ); }
内容的提问来源于stack exchange,提问作者Ăżiž
相关产品推荐
相关产品推荐

