ui icon indicating copy to clipboard operation
ui copied to clipboard

Page becomes unresponsive after closing a modal

Open TA9IO opened this issue 1 year ago • 10 comments

I want a dropdown-menu with two options: delete and edit, When the user selects an option, a popover will show to confirm his selection. I know we shouldn't put the dialog inside the dropdown menu other wise the popover will not even be vissible on the page, but I also had another issue: the page becomes unresponsive after a modal is closed. This is a video of the issue; the code is copied from the examples/playground page, where it functions properly. i have this probleme only when when the modal is opened from a dropdown thanks yall

https://github.com/shadcn-ui/ui/assets/93702837/287bc707-df04-4831-a31c-f25e9e3a3ddf

TA9IO avatar Nov 08 '23 13:11 TA9IO

Having this issue too. Repeat of #1859.

hummify avatar Nov 08 '23 23:11 hummify

I have been facing the same issue. Did you find a solution?

bmutahhar avatar Nov 14 '23 11:11 bmutahhar

For now I've just put !pointer-events-auto in the className for body, but it's hacky and temporary.

hummify avatar Nov 14 '23 11:11 hummify

Adding modal={false} on dropdown menu root worked for me!

bmutahhar avatar Nov 14 '23 11:11 bmutahhar

Setting modal={false} will allow users to interact with elements outside of the dialog while it is open which I don't think is usually desirable!

hummify avatar Nov 14 '23 11:11 hummify

I have the same issues. What happened to me was that I had a dialog triggered in a dropdown menu, and when the dialog was closed, all the clickable elements on page were not clickable.

microflyer avatar Nov 16 '23 06:11 microflyer

Yeah it seems to only be an issue when triggering a dialog from a dropdown menu. It's as if the pointer-events: none class is being toggled. Clicking on the dropdown menu adds pointer-events: none to body, then selecting an item to open the dialog removes the class, and then closing the dialog adds the class back again. I've found related issue with a lack of automatic focus inside the dialog when it's triggered from a dropdown menu... But don't have a solution as of yet.

hummify avatar Nov 16 '23 12:11 hummify

Then again, I haven't been using this implementation which has been passed around quite a bit in the past:

https://codesandbox.io/embed/r9sq1q

hummify avatar Nov 16 '23 12:11 hummify

hi all,

I just found a solution: https://github.com/radix-ui/primitives/issues/837#issuecomment-1455160154

here's a example of shadcn-ui:

<DropdownMenu>
  <DropdownMenuTrigger asChild>
    <Button variant="outline">
      <span className="sr-only">Actions</span>
      <MoreHorizontal className="h-4 w-4" />
    </Button>
  </DropdownMenuTrigger>
  <DropdownMenuContent align="end">
    <AlertDialog
      open={showDeleteDialog}
      onOpenChange={setShowDeleteDialog}
    >
      <AlertDialogTrigger asChild>
        <DropdownMenuItem
          className="text-red-600"
          onSelect={(event) => {
            event.preventDefault();
            setShowDeleteDialog(true);
          }}
        >
          <Trash className="mr-2 h-4 w-4" />
          Delete
        </DropdownMenuItem>
      </AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
          <AlertDialogDescription>
            This action cannot be undone. The document will no longer be
            accessible by you or others you&apos;ve shared it with.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Cancel</AlertDialogCancel>
          <Button
            variant="destructive"
            onClick={() => {
              deleteSelectedFiles();
              setShowDeleteDialog(false);
            }}
          >
            Delete
          </Button>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  </DropdownMenuContent>
</DropdownMenu>

Two main points:

  • have event.preventDefault(); in the DropdownMenuItem. This actually prevents the dialog closing due to the dropdown menu closing.
  • Wrap AlertDialog in DropdownMenuContent

stimw avatar Nov 24 '23 15:11 stimw

Two main points:

  • have event.preventDefault(); in the DropdownMenuItem. This actually prevents the dialog closing due to the dropdown menu closing.
  • Wrap AlertDialog in DropdownMenuContent

I got this working also but instead with ContextMenu and Sheet.

This solution has worked, thank you @stimw ❤️

d4vidsha avatar Dec 23 '23 03:12 d4vidsha

This issue has been automatically closed because it received no activity for a while. If you think it was closed by accident, please leave a comment. Thank you.

shadcn avatar Feb 12 '24 23:02 shadcn

This issue is still present, and manually managing state is not a viable solution imo.

thvozdovic avatar Feb 25 '24 19:02 thvozdovic

I'm unsure why this is got closed, but I'd like this to be revisited again. This is issue is still present. Once I close the sheet the entire page is unresponsive.

I have a hard time believing that the code below is suggested because the entire page still freezes, and if I wanted to have more that one sheet open from inside the dropdown menu i'm SOL? Am I missing something?

    return (
      <Sheet>
        <DropdownMenu>
          <DropdownMenuTrigger asChild>
            <Button size="icon" variant="outline" className="h-8 w-8">
              <MoreHorizontal className="h-3.5 w-3.5" />
              <span className="sr-only">More</span>
            </Button>
          </DropdownMenuTrigger>
          <DropdownMenuContent align="end" className="w-52">
            <SheetTrigger asChild>
              <DropdownMenuItem>
                <Pencil className="mr-2 h-4 w-4" />
                Edit Profile
                <DropdownMenuShortcut>⌘ + E</DropdownMenuShortcut>
              </DropdownMenuItem>
            </SheetTrigger>
            <DropdownMenuItem>
              <Trash className="mr-2 h-4 w-4" />
              Trash
            </DropdownMenuItem>
          </DropdownMenuContent>
        </DropdownMenu>
        <SheetContent>
          <EditProfile name="Jason Anrico" username="anricoj1" />
        </SheetContent>
      </Sheet>
    );

anricoj1 avatar Apr 02 '24 18:04 anricoj1

Updating from "@radix-ui/react-dialog": "^1.0.4" to 1.0.5 worked for me. You might as well update "@radix-ui/react-alert-dialog": "^1.0.4" to 1.0.5 as well too.

anricoj1 avatar Apr 03 '24 14:04 anricoj1

Adding this as well for anyone who comes to this thread looking for a more useful solution. I ended up making a SheetItem and SheetItemTrigger components as well as AlertDialogItem, AlertDialogItemTrigger, DialogItem, and DialogItemTrigger variants in the case where you might want to have multiple dialog options from one dropdown menu. Lets face it, if you're opening one dialog in the menu, you'll probably end up adding more in the future.

/** sheet within dropdown menu item */
function SheetItem({
  onOpenChange,
  children,
  ...props
}: ComponentPropsWithoutRef<typeof Sheet>) {
  return (
    <Sheet onOpenChange={onOpenChange} {...props}>
      {children}
    </Sheet>
  );
}

/** trigger sheet from dropdown menu */
const SheetItemTrigger = forwardRef<
  ElementRef<typeof DropdownMenuItem>,
  ComponentPropsWithoutRef<typeof DropdownMenuItem>
>(({ onSelect, children, ...props }, ref) => {
  return (
    <SheetTrigger asChild>
      <DropdownMenuItem
        {...props}
        ref={ref}
        onSelect={(event) => {
          event.preventDefault();
          onSelect && onSelect(event);
        }}
      >
        {children}
      </DropdownMenuItem>
    </SheetTrigger>
  );
});
SheetItemTrigger.displayName = "SheetItemTrigger";

If you want to leverage AlertDialogs and Dialogs you need the same code but replace Sheet with AlertDialog and Dialog. So now I can write a DropdownMenu that contains any of these, if not all of them and it works as expected

    return (
      <DropdownMenu>
        <DropdownMenuTrigger asChild>
          <Button variant="outline">Open</Button>
        </DropdownMenuTrigger>
        <DropdownMenuContent className="w-56">
          <DropdownMenuLabel>Dialog Options</DropdownMenuLabel>
          <DropdownMenuSeparator />
          <DropdownMenuGroup>
            <AlertDialogItem>
              <AlertDialogItemTrigger>
                <FileWarning className="mr-2 h-4 w-4" />
                <span>Alert Dialog</span>
                <DropdownMenuShortcut>⇧⌘A</DropdownMenuShortcut>
              </AlertDialogItemTrigger>
              <AlertDialogContent>
                <AlertDialogHeader>
                  <AlertDialogTitle>Edit Profile</AlertDialogTitle>
                  <AlertDialogDescription>
                    Update your profile information.
                  </AlertDialogDescription>
                </AlertDialogHeader>
                <EditProfileForm name="Jason Anrico" username="anricoj1" />
                <AlertDialogFooter>
                  <AlertDialogAction>Save</AlertDialogAction>
                  <AlertDialogCancel>Cancel</AlertDialogCancel>
                </AlertDialogFooter>
              </AlertDialogContent>
            </AlertDialogItem>
            <DialogItem>
              <DialogItemTrigger>
                <MessageCircle className="mr-2 h-4 w-4" />
                <span>Dialog</span>
                <DropdownMenuShortcut>⌘D</DropdownMenuShortcut>
              </DialogItemTrigger>
              <DialogContent>
                <DialogHeader>
                  <DialogTitle>Edit Profile</DialogTitle>
                  <DialogDescription>
                    Update profile information.
                  </DialogDescription>
                </DialogHeader>
                <EditProfileForm name="Jason Anrico" username="anricoj1" />
                <DialogFooter>
                  <Button variant="outline">Save</Button>
                  <Button variant="outline">Cancel</Button>
                </DialogFooter>
              </DialogContent>
            </DialogItem>
            <SheetItem>
              <SheetItemTrigger>
                <FileSpreadsheet className="mr-2 h-4 w-4" />
                <span>Sheet</span>
                <DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
              </SheetItemTrigger>
              <SheetContent>
                <SheetHeader>
                  <SheetTitle>Edit Profile</SheetTitle>
                  <SheetDescription>
                    Update profile information.
                  </SheetDescription>
                </SheetHeader>
                <EditProfileForm name="Jason Anrico" username="anricoj1" />
                <SheetFooter>
                  <Button variant="outline">Save</Button>
                  <Button variant="outline">Cancel</Button>
                </SheetFooter>
              </SheetContent>
            </SheetItem>
          </DropdownMenuGroup>
        </DropdownMenuContent>
      </DropdownMenu>
    )

Hope this is helpful information to someone in the future!

anricoj1 avatar Apr 03 '24 15:04 anricoj1

@anricoj1 suggestion by updating the dependencies for dialog and alert dialog to 1.0.5 fixed it for me

FadiAboMsalam avatar May 08 '24 12:05 FadiAboMsalam

it's happening again in version @radix-ui/react-dialog 1.1.1

oacevedo avatar Jun 23 '24 18:06 oacevedo

I can confirm it is happening in 1.1.1 as well.

As a workaround I force the pointerEvents on the body set by the context menu/dropdown to be reset.

    <Dialog
      open={show}
      onOpenChange={(open) => {
        setShow(open)
        setTimeout(() => {
          if (!open) {
            document.body.style.pointerEvents = ''
          }
        }, 100)
      }}
    >

IMHO, it is better than wrapping a dialog around the entire context menu, which is very odd to me.

yagudaev avatar Jun 24 '24 21:06 yagudaev

This still seems to be an issue using [email protected] and [email protected].

I was able to fix the problem by introducing two separate states, one that tracks the state of the Dialog, the other one for the DropdownMenu:

const [isDialogOpen, setIsDialogOpen] = useState(false);
const [isDropdownOpen, setIsDropdownOpen] = useState(false);

Then apply the onOpenChange handler to the DropdownMenu...

<DropdownMenu onOpenChange={setIsDropdownOpen}>

...and only open the Dialog, if the dropdown is not open:

<Dialog
    open={isDialogOpen && !isDropdownOpen}
    onOpenChange={setIsDialogOpen}
>

Works perfectly fine without placing the dialog inside the Dropdown menu or the need for a setTimeout.

tomraithel avatar Jun 28 '24 15:06 tomraithel

@yagudaev @tomraithel combining both of your approaches has worked for me. I did not find using two states sufficient, as the UI was still unresponsive after closing the dialog. Here's a full working example with [email protected] and [email protected] :

import React, { useState } from 'react';
import { useForm } from 'react-hook-form';
import {
    DropdownMenu,
    DropdownMenuContent,
    DropdownMenuItem,
    DropdownMenuTrigger,
} from "@/src/components/shadcn/ui/dropdown-menu";
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, } from "@/src/components/shadcn/ui/dialog";
import { Button } from "@/src/components/shadcn/ui/button";

function TemplateDropdownMenu() {

    const [isDialogOpen, setIsDialogOpen] = useState(false);
    const [isDropdownOpen, setIsDropdownOpen] = useState(false);
    const {register, handleSubmit, reset} = useForm();

    const onSubmit = (data: any) => {
        console.log(data);
        setIsDialogOpen(false)
    };

    return (
        <>
            <Dialog
                open={isDialogOpen && !isDropdownOpen}
                onOpenChange={(open) => {
                    setIsDialogOpen(open)
                    setTimeout(() => {
                        if (!open) {
                            document.body.style.pointerEvents = ''
                        }
                    }, 100)
                }}
            >
                <DialogContent>
                    <DialogHeader>
                        <DialogTitle>Dialog Form</DialogTitle>
                    </DialogHeader>
                    <form onSubmit={handleSubmit(onSubmit)}>
                        <input {...register("example")} />
                        <DialogFooter>
                            <Button type="submit">Submit</Button>
                        </DialogFooter>
                    </form>
                </DialogContent>
            </Dialog>
            <DropdownMenu onOpenChange={setIsDropdownOpen}>
                <DropdownMenuTrigger>Open Menu</DropdownMenuTrigger>
                <DropdownMenuContent>
                    <DropdownMenuItem onClick={() => setIsDialogOpen(true)} >Edit</DropdownMenuItem>
                </DropdownMenuContent>
            </DropdownMenu>
        </>
    );
}

export default TemplateDropdownMenu;

lmyslinski avatar Jul 02 '24 06:07 lmyslinski

@yagudaev @tomraithel combining both of your approaches has worked for me. I did not find using two states sufficient, as the UI was still unresponsive after closing the dialog. Here's a full working example with [email protected] and [email protected] :


import React, { useState } from 'react';

import { useForm } from 'react-hook-form';

import {

    DropdownMenu,

    DropdownMenuContent,

    DropdownMenuItem,

    DropdownMenuTrigger,

} from "@/src/components/shadcn/ui/dropdown-menu";

import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, } from "@/src/components/shadcn/ui/dialog";

import { Button } from "@/src/components/shadcn/ui/button";



function TemplateDropdownMenu() {



    const [isDialogOpen, setIsDialogOpen] = useState(false);

    const [isDropdownOpen, setIsDropdownOpen] = useState(false);

    const {register, handleSubmit, reset} = useForm();



    const onSubmit = (data: any) => {

        console.log(data);

        setIsDialogOpen(false)

    };



    return (

        <>

            <Dialog

                open={isDialogOpen && !isDropdownOpen}

                onOpenChange={(open) => {

                    setIsDialogOpen(open)

                    setTimeout(() => {

                        if (!open) {

                            document.body.style.pointerEvents = ''

                        }

                    }, 100)

                }}

            >

                <DialogContent>

                    <DialogHeader>

                        <DialogTitle>Dialog Form</DialogTitle>

                    </DialogHeader>

                    <form onSubmit={handleSubmit(onSubmit)}>

                        <input {...register("example")} />

                        <DialogFooter>

                            <Button type="submit">Submit</Button>

                        </DialogFooter>

                    </form>

                </DialogContent>

            </Dialog>

            <DropdownMenu onOpenChange={setIsDropdownOpen}>

                <DropdownMenuTrigger>Open Menu</DropdownMenuTrigger>

                <DropdownMenuContent>

                    <DropdownMenuItem onClick={() => setIsDialogOpen(true)} >Edit</DropdownMenuItem>

                </DropdownMenuContent>

            </DropdownMenu>

        </>

    );

}



export default TemplateDropdownMenu;

Interesting, I thought the approach of the two states by @tomraithel would work.

Looks like it waits for the context menu to properly close before opening the dialog which should make the problem go away.

Anyway, I'll keep an eye on this. We should re-open this issue.

yagudaev avatar Jul 02 '24 17:07 yagudaev

Yeah strange, I wonder why it works in my case but for @lmyslinski it does not (without the timeout)... 🤔

tomraithel avatar Jul 02 '24 18:07 tomraithel

Yeah strange, I wonder why it works in my case but for @lmyslinski it does not (without the timeout)... 🤔

your fix worked for me perfectly as well, is it because our dialog is outside the dropdown menu? mine is.
thanks for the fix @tomraithel

corned-beefhash avatar Jul 03 '24 09:07 corned-beefhash

Can confirm by pinning to 1.0.5 fixes the bug 🫡

npm i @radix-ui/[email protected]

just make sure to set a reminder to update it once it's fixed!

eagleeyejack avatar Aug 07 '24 08:08 eagleeyejack