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
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
and onChangeOpen
props to dropdown component.
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);
};
const [isDropDownOpen, setIsDropDownOpen] = useState(false);
return (
<div>
<DropdownMenu open={isDropDownOpen} onOpenChange={() => {
setIsDropDownOpen(!isDropDownOpen);
}}>
<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={(e) => {
e.stopPropagation(); // Add this line to stop event propagation
setIsDropDownOpen(false);
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>
);
}