skip to Main Content

I’m using shadcn/ui in my React Remix app, and I’m trying to prevent it from redirecting to another page when I click to open a dialog. I want the redirection to still work when I click on it, but I want to prevent the page from redirecting when I intend to open the dialog

Codesandbox —-> https://codesandbox.io/p/devbox/shadcn-ui-vite-forked-mptc3y?file=%2Fsrc%2FApp.tsx%3A17%2C2-41%2C13

 <div className="flex flex-col border-solid border rounded-md mt-10">
    {data.map((item) => {
      return (
        <div key={item.id}>
          <a href={`/inbox/${item.id}`}>
            <div className="flex flex-col items-start gap-2 p-3 text-left text-sm transition-all w-full border-b hover:shadow-md cursor-pointer">
              <div className="flex w-full flex-col gap-1">
                <div className="flex items-center">
                  <div className="flex items-center gap-2">
                    <div className="font-semibold">{item.name}</div>
                  </div>
                </div>
              </div>

              <div className="flex w-full flex-col gap-1">
                <div className="flex items-center">
                  <ApproveDialog id={"some test"} />
                </div>
              </div>
            </div>
          </a>
        </div>
      );
    })}
  </div>

3

Answers


  1. A <a> tag is probably not the best fit for what you are trying to achieve as it is intended to be a hyperlink that navigates elsewhere. Using another tag like button might be a better idea in this case.
    However, if you really want to stop the a tag from navigating away, you may want to include a onClick={ev => ev.preventDefault()} on that a tag.

    Check out ev.preventDefault() documentation.

    Login or Signup to reply.
  2. There’s a reason it is recommended not to place interactive elements like buttons inside/within other interactive elements like anchor tags. You could try stopping the click event propagation of the dialog open button from propagating up the DOMTree and hitting the anchor tag, but I couldn’t get this to work as expected in your sandbox (which I suspect may be due to how the Dialog components are implemented).

    The logical solution is to un-nest these interactive elements so they can function normally as expected in isolation from one another. Refactor the Inbox UI a bit to render the ApproveDialog outside the link.

    Example:

    const data = [ .... ];
    
    export default function Inbox() {
      return (
        <div>
          <h1 className="text-xl font-bold">Inbox ({data.length})</h1>
    
          <div className="flex flex-col border-solid border rounded-md mt-10">
            {data.map((item) => {
              return (
                <div key={item.id} className="flex flex-col items-start gap-2 p-3 text-left text-sm transition-all w-full border-b hover:shadow-md cursor-pointer">
                  <a href={`/inbox/${item.id}`} className="flex w-full flex-col gap-1">
                    <div className="flex items-center">
                      <div className="flex items-center gap-2">
                        <div className="font-semibold">{item.name}</div>
                      </div>
                    </div>
                  </a>
                    
                  <div className="flex w-full flex-col gap-1">
                    <div className="flex items-center">
                      <ApproveDialog id={"some test"} />
                    </div>
                  </div>
                </div>
              );
            })}
          </div>
        </div>
      );
    }
    

    The rendered content remains visually identical, but now the button is rendered outside the link and won’t interfere with navigation when all you are trying to do is open the modal/dialog.

    enter image description here
    enter image description here
    enter image description here

    Alternative

    Update the logic to make the Dialog component fully controlled and totally prevent the default action on the Button component.

    Example:

    export default function ApproveDialog({ id }: { id: string }) {
      const [isOpen, setIsOpen] = useState(false);
    
      return (
        <Dialog open={isOpen} onOpenChange={setIsOpen}>
          <DialogTrigger asChild>
            <Button
              onClick={(e) => {
                e.preventDefault();
                setIsOpen(true);
              }}
              size="sm"
              className="rounded-full"
            >
              Send
            </Button>
          </DialogTrigger>
          <DialogContent className="sm:max-w-[425px]">
            <DialogHeader>
              <DialogTitle>Ready to send?</DialogTitle>
              <DialogDescription>
                Do you want to send this message to the recipient?
              </DialogDescription>
            </DialogHeader>
            <DialogFooter>
              <DialogClose asChild>
                <Button
                  size="sm"
                  className="bg-success hover:bg-success text-white"
                  type="submit"
                  onClick={async () => {
                    const response = await fetch("/api/approve", {
                      method: "POST",
                      body: JSON.stringify({ id }),
                    });
                    if (!response.ok) {
                      alert("error");
                      return;
                    }
                    alert("success");
                  }}
                >
                  <Send size={15} className="mr-2" /> Send
                </Button>
              </DialogClose>
              <DialogClose asChild>
                <Button size="sm" variant={"outline"}>
                  Cancel
                </Button>
              </DialogClose>
            </DialogFooter>
          </DialogContent>
        </Dialog>
      );
    }
    
    Login or Signup to reply.
  3. Try bellow code

    App.tsx

    export default function Inbox() {
     const data = [
       {
         id: 1,
         name: "hello",
       },
     ];
    
     return (
       <div>
         <h1 className="text-xl font-bold">Inbox ({data.length})</h1>
         <div className="flex flex-col mt-10 border border-solid rounded-md">
           {data.map((item) => {
             return (
               <div key={item.id}>
                 <div
                   onClick={(e) => {
                     window.location.href = "sign-in";
                   }}
                 >
                   <div className="flex flex-col items-start w-full gap-2 p-3 text-sm text-left transition-all border-b cursor-pointer hover:shadow-md">
                     <div className="flex flex-col w-full gap-1">
                       <div className="flex items-center">
                         <div className="flex items-center gap-2">
                           <div className="font-semibold">{"item.name"}</div>
                         </div>
                       </div>
                     </div>
    
                     <div className="flex flex-col w-full gap-1">
                       <div className="flex items-center">
                         
                       </div>
                     </div>
                   </div>
                 </div>
               </div>
             );
           })}
         </div>
       </div>
     );
    }
    

    ApproveDialog.tsx

    export default function ApproveDialog({ id }: { id: string }) {
      return (
        <Dialog>
          <DialogTrigger asChild>
            <Button
              size="sm"
              className="rounded-full"
              onClick={(e) => {
                e.stopPropagation();
              }}
            >
              Send
            </Button>
          </DialogTrigger>
          <DialogContent className="sm:max-w-[425px]">
            <DialogHeader>
              <DialogTitle>Ready to send?</DialogTitle>
              <DialogDescription>
                Do you want to send this message to the recipient?
              </DialogDescription>
            </DialogHeader>
            <DialogFooter>
              <DialogClose asChild>
                <Button
                  size="sm"
                  className="text-white bg-success hover:bg-success"
                  type="submit"
                  onClick={async () => {
                    const response = await fetch("/api/approve", {
                      method: "POST",
                      body: JSON.stringify({ id }),
                    });
                    if (!response.ok) {
                      alert("error");
                      return;
                    }
                    alert("success");
                  }}
                >
                  <Send size={15} className="mr-2" /> Send
                </Button>
              </DialogClose>
              <DialogClose asChild>
                <Button size="sm" variant={"outline"}>
                  Cancel
                </Button>
              </DialogClose>
            </DialogFooter>
          </DialogContent>
        </Dialog>
      );
    }
    

    Update your DialogOverlay as bellow:

    (Dialog.tsx)

    
    const DialogOverlay = React.forwardRef<
      React.ElementRef<typeof DialogPrimitive.Overlay>,
      React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
    >(({ className, ...props }, ref) => (
      <DialogPrimitive.Overlay
        ref={ref}
        className={cn(
          "fixed inset-0 z-50 bg-black/80  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
          className
        )}
        {...props}
        onClick={(e) => {
          e.stopPropagation();
        }}
      />
    ))
    DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
     
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search