javascriptreactjsnext.js

Shadcn Dialog inside of Dropdown closes automatically


I use shadcn in my next.js 13 project. I want to have a dropdown with the option to edit or delete an entry. When the user clicks on "delete" a dialog should pop up and ask them for a confirmation. However, the dialog only shows for about 0.5 seconds before it closes together with the dropdown. How can I prevent that from happening?

Here is the example on codesandbox: Codesandbox

This is the code:

    <DropdownMenu>
      <DropdownMenuTrigger>
        <p>Trigger</p>
      </DropdownMenuTrigger>
      <DropdownMenuContent>
        <Dialog>
          <DropdownMenuLabel>Edit Entry</DropdownMenuLabel>
          <DropdownMenuSeparator />
          <DropdownMenuItem
            onClick={() => conosle.log("Navigate to edit page")}
          >
            Edit
          </DropdownMenuItem>
          <DialogTrigger>
            <DropdownMenuItem>Delete</DropdownMenuItem>
          </DialogTrigger>
          <DialogContent>
            <DialogHeader>
              <DialogTitle>Are you sure?</DialogTitle>
              <DialogDescription>
                Do you want to delete the entry? Deleting this entry cannot be
                undone.
              </DialogDescription>
            </DialogHeader>
            <DialogFooter>
              <DialogClose asChild>
                <Button variant="outline">Cancel</Button>
              </DialogClose>
              <Button>Delete</Button>
            </DialogFooter>
          </DialogContent>
        </Dialog>
      </DropdownMenuContent>
    </DropdownMenu>

Solution

  • What is the problem?

    When you click any <DropdownMenuItem />, It will trigger the action (onClick) and close (unmount) the <DropdownMenuContent /> which includes the <DialogContent /> so it'll be unmounted with it.

    Solutions

    1. Move the <DialogContent /> outside of the <DropdownMenuContent />

    // ...
    export default function App() {
      return (
        <Dialog> {/* 🔴 The dialog provider outside of the DropdownMenuContent */}
          <DropdownMenu>
            <DropdownMenuTrigger>
              <p>Trigger</p>
            </DropdownMenuTrigger>
            <DropdownMenuContent>
              <DropdownMenuItem>
                <DialogTrigger>
                  Open Popup
                </DialogTrigger>
              </DropdownMenuItem>
            </DropdownMenuContent>
          </DropdownMenu>
          {/* 🔴 DialogContent ouside of DropdownMenuContent */}
          <DialogContent>
            <DialogHeader>
              <DialogTitle>Are you sure?</DialogTitle>
              <DialogDescription>
                Do you want to delete the entry? Deleting this entry cannot be
                undone.
              </DialogDescription>
            </DialogHeader>
            <DialogFooter>
              <DialogClose asChild>
                <Button variant="outline">Cancel</Button>
              </DialogClose>
              <Button>Delete</Button>
            </DialogFooter>
          </DialogContent>
        </Dialog>
      );
    }
    

    This solution works well if you have a single item triggers dialog. But what if you have multiple dialogs?

    2. Multiple dialogs

    Move your dialog outside the <DropdownMenuContent />, create a state for each one:

    const [isEditDialogOpen, setIsEditDialogOpen] = useState(false)
    const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false)
    

    then remove any <DialogTrigger />, add onClick instead

    <DropdownMenuItem onClick={() => setIsEditDialogOpen(true)}>Edit</DropdownMenuItem>
    <DropdownMenuItem onClick={() => setIsDeleteDialogOpen(true)}>Delete</DropdownMenuItem>
    

    In your dialog add

    <Dialog open={isEditDialogOpen || isDeleteDialogOpen} 
            onOpenChange={isEditDialogOpen ? 
                setIsEditDialogOpen : setIsDeleteDialogOpen}>
    ...
    </Dialog>
    

    If you don't want to make it controlled and can render two trigger buttons, you can render two separate dialogs:

    <Dialog>
      <DialogTrigger>Edit Post</DialogTrigger>
      <DialogContent>
         Content 
      </DialogContent>
    </Dialog>
    
    <Dialog>
      <DialogTrigger>Edit Post</DialogTrigger>
      <DialogContent>
        Content 
      </DialogContent>
    </Dialog>