You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js中Shadcn AlertDialog Cancel按钮onClick无法关闭的问题

问题:Shadcn AlertDialog取消按钮无法关闭对话框(嵌套在DropdownMenu中)

开发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>
  );
}

问题原因

  1. 双重触发逻辑冲突:在DropdownMenuItem上绑定了onClick来打开对话框,同时内部又放置了AlertDialogTrigger,导致AlertDialog的内部状态和外部的isDialogOpen状态不一致。
  2. 事件冒泡与状态同步问题: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ž

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 01:35:01