skip to Main Content

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


  1. Chosen as BEST ANSWER

    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, the DropdownMenuItem).

    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:

    <AlertDialogCancel
      onClick={(e) => {
        e.stopPropagation(); // Add this line to stop event propagation
        setIsDialogOpen(false);
      }}
    >
      Cancel
    </AlertDialogCancel>
    

  2. 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>
      );
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search