I am working on a Next.js app, and in one of the components, I have a DropdownMenu
where one of its items launches a DialogAlert
(both are Shadcn components). I am trying to control the dialog’s open state with setIsDialogOpen
, but it only works when there is a synchronous function before it (like the confirm button example). Otherwise, it won’t work when I simply call setIsDialogOpen(false)
from the cancel button action.
Here is the code to reproduce the problem:
"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);
// Perform your delete operation here
await new Promise((resolve) => setTimeout(resolve, 2000)); // Simulating an async operation
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(); // Prevent default action that closes the dialog
await handleDelete();
setIsDialogOpen(false);
}}
>
Continue
</AlertDialogAction>
)}
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
);
}
Here is a CodeSandbox link to reproduce the issue: https://codesandbox.io/p/devbox/cranky-montalcini-4432z5?embed=1&file=%2Fapp%2Fpage.tsx%3A38%2C20
2
Answers
The issue I was facing is related to the
event propagation in React
. When you click the "Cancel" button, the click event is propagating up to the parent component (in this case, theDropdownMenuItem
).To fix this, I had to stop the event propagation by calling
event.stopPropagation()
in the onClick handler of the "Cancel" button. Here's the updated code of that part:Maybe you also wants to close the dropdown on press of cancel button in alert dialog. To achieve this you need to convert the dropdown component from uncontrolled to controlled component by passing
open
andonChangeOpen
props to dropdown component.