Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: function to trigger open/close dialog #386

Closed
madatbay opened this issue May 20, 2023 · 22 comments
Closed

feat: function to trigger open/close dialog #386

madatbay opened this issue May 20, 2023 · 22 comments

Comments

@madatbay
Copy link

Tried to search for it but didn't find a way to close the dialog with a method or function. This is useful after processing data like - making a fetch request, mutation, or something that is awaitable, or can change dialog state from outside
In the example below, let's assume when the user clicks to "save changes", I want to handle some submit function as a result close dialog from that function.

... await updateData(data).then(res=>closeDialog()).catch(err=>setError(error))

Currently, the way I see is used after exporting Close from radix primitive. But this is a JSX element, cant be used as function to change dialog state

<DialogClose>Close<<DialogClose/>
Screenshot 2023-05-20 at 09 37 14

There is 2nd way to close Dialog by creating a state and binding it to open={open} prop in the Dialog component. We can open or close Dialog by changing that state. But when do so, the "X" button and clicking outside of the dialog to close it stops working

Why we need that feature?

I think this will solve the issue we have in Note section which asserts that we have to encase the trigger button to Dialog itself. I take this as a big problem because if I have a Dialog component, it's not possible to trigger it from multiple places. If I need to have that dialog in multiple places I need to copy/paste the same dialog code which is a duplication issue.

Sample use case: I have "Newsletter dialog" and I need to trigger this dialog with the button in the navbar, footer, or inside the main content. Or if the user stays for 5 mins on the website, I want to trigger that newsletter dialog

@chungweileong94
Copy link

chungweileong94 commented May 20, 2023

If you want control the opening state by yourself, the open prop is the way to go, however, you also need to set the onOpenChange, so that the close button wiil able to trigger the onOpenChange event and change the state that you created.

Example:

const [open, setOpen] = useState(false);

return <Dialog open={open} onOpenChange={setOpen} />

Reference:
https://www.radix-ui.com/docs/primitives/components/dialog#root

@OsamaQureshi147
Copy link

I am using Next13.4 app router in which all the components and pages are RSC and SSR respectively by default. I am rendering the Dialog in the custom component lets say AddToCartDialog.tsx in server component and want to close it programmatically something like handleCloseDialog().
I can't pass the prop state and setter function as React hooks do not work inside React Server Components. Is there clean way to do it? Or should I add a redundant client component wrapper for the dialogs?

@chungweileong94
Copy link

@OsamaQureshi147 It seems like you need JS on client side to me tho, this is the time where you have to use client component instead.

@wobsoriano
Copy link
Contributor

wobsoriano commented Sep 29, 2023

@OsamaQureshi147 For a basic requirement, you can probably use the searchParams and do a condition like:

/some-page?productId=69&modal=true

export default function Page({
  searchParams,
}: {
  searchParams: { [key: string]: string | string[] | undefined }
}) {
  const { productId, modal } = searchParams

  return (
    <div>
      <h1>Some Page</h1>
      <YourDialog open={modal === "true"} productId={productId} />
    </div>
  )
}

Then use <Link href="/some-page" /> to dismiss the modal.

Other than that, client component.

Demo:

Screen.Recording.2023-09-29.at.4.14.55.PM.mov

@msbeeman
Copy link

@wobsoriano The problem is you can't use this approach if you're trying to open a modal from any components that generally live in an applications root layout such as a Navbar since search params are not available in that context, but I think it's good approach for subpages.

Source: https://nextjs.org/docs/app/api-reference/file-conventions/layout#layouts-do-not-receive-searchparams

@wobsoriano
Copy link
Contributor

wobsoriano commented Sep 30, 2023

Right @msbeeman, I guess you can still use useSearchParams in the layout.

@OsamaQureshi147 this might be worth checking as well - Intercepting Routes

Example - https://nextjs-app-route-interception.vercel.app/

@Steveb599
Copy link

@OsamaQureshi147 For a basic requirement, you can probably use the searchParams and do a condition like:

/some-page?productId=69&modal=true

export default function Page({
  searchParams,
}: {
  searchParams: { [key: string]: string | string[] | undefined }
}) {
  const { productId, modal } = searchParams

  return (
    <div>
      <h1>Some Page</h1>
      <YourDialog open={modal === "true"} productId={productId} />
    </div>
  )
}

Then use <Link href="/some-page" /> to dismiss the modal.

Other than that, client component.

Demo:

Screen.Recording.2023-09-29.at.4.14.55.PM.mov

How can I make a required fields in the modal?

@zacBkh
Copy link

zacBkh commented Dec 16, 2023

If you want control the opening state by yourself, the open prop is the way to go, however, you also need to set the onOpenChange, so that the close button wiil able to trigger the onOpenChange event and change the state that you created.

Example:

const [open, setOpen] = useState(false);

return <Dialog open={open} onOpenChange={setOpen} />

Reference: https://www.radix-ui.com/docs/primitives/components/dialog#root

thanks, not so clear in the doc!

@quyettranvu
Copy link

If you want control the opening state by yourself, the open prop is the way to go, however, you also need to set the onOpenChange, so that the close button wiil able to trigger the onOpenChange event and change the state that you created.

Example:

const [open, setOpen] = useState(false);

return <Dialog open={open} onOpenChange={setOpen} />

Reference: https://www.radix-ui.com/docs/primitives/components/dialog#root

Thank you very much, after searching and trying for couples of hours, I finally could reach it!

@wobsoriano
Copy link
Contributor

wobsoriano commented Jan 8, 2024

@chungweileong94
Copy link

I'm actually trying a different approach (https://github.com/chungweileong94/nextjs-parallel-route-dialog) by using the NextJS parallel route to control the dialog open state. The thing that I focus in my code example is to preserve the dialog closing animation.

@sobitp59
Copy link

This is how we can do, hope it helps

"use client";
// Imports here

export function AddProductModal() {
  const router = useRouter();
  const [open, setOpen] = useState(false);

  return (
    <Dialog open={open} onOpenChange={setOpen}>
      <DialogTrigger asChild>
        <Button
          className="bg-red-50 text-red-600"
        >
          Add Product
        </Button>
      </DialogTrigger>
      <DialogContent className="sm:max-w-[425px]">
         // ADD PRODUCT FORM HERE
        <DialogFooter>
          <Button
            onClick={() => {
              addProduct(data).then(() => setOpen(false));
              router.refresh();
            }}
            className="bg-red-50 text-red-600 hover:bg-red-100"
            type="submit"
          >
            Add
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

@jsaunders92
Copy link

I'm actually trying a different approach (https://github.com/chungweileong94/nextjs-parallel-route-dialog) by using the NextJS parallel route to control the dialog open state. The thing that I focus in my code example is to preserve the dialog closing animation.

I was doing it like you, but slightly differently. I changed my code to match your approach though and I think it's more manageable/closer to what I want. The thing is, the closing animation does work, but sometimes. I wonder if there is some sort of race condition that is causing it to sometimes not work?

@chungweileong94
Copy link

I wonder if there is some sort of race condition that is causing it to sometimes not work?

Well, it works fine to fit my use-case. But I don't expect the close animation to work in cases like hard navigation.
I do notice some limitation to the close animation, where the content of the dialog actually close instantly as soon as it triggers, but the animation is fast enough that you wouldn't notice it😬

You could try to use the browser history API with NextJS 14.1, to see if that helps, https://nextjs.org/blog/next-14-1#windowhistorypushstate-and-windowhistoryreplacestate, haven't try it myself personally.

But I will say if you really want to get the animation right, move things to client-side is the way to go.

@jsaunders92
Copy link

You could try to use the browser history API with NextJS 14.1, to see if that helps, https://nextjs.org/blog/next-14-1#windowhistorypushstate-and-windowhistoryreplacestate, haven't try it myself personally.

I'll give this a go, I'll report back! I've already been messing around with the router.

But I will say if you really want to get the animation right, move things to client-side is the way to go.
I think I much rather not. To the point, I'm willing to sacrifice the exit animation. Although I personally think it's easily doable. I mean it's like 90% there anyway already. I just need to work out what causes it to sometimes not work.

Either way, my code and then the changes based on your reorganisation of the folder structure based on your example has been really useful. So has Ariakit's Dialog with App Router documentation. I'll let you know if I get further with the exit animations.

@nishaaanth2
Copy link

If you want control the opening state by yourself, the open prop is the way to go, however, you also need to set the onOpenChange, so that the close button wiil able to trigger the onOpenChange event and change the state that you created.

Example:

const [open, setOpen] = useState(false);

return <Dialog open={open} onOpenChange={setOpen} />

Reference: https://www.radix-ui.com/docs/primitives/components/dialog#root

bruh, i suck
i was try digging in wrong "dialogtrigger"
wasted 3 days because of this. open dialog

@harsh7800
Copy link

{!form.formState.isValid ? ( <Button type="submit" className="w-full"> Save changes </Button> ) : ( <DialogClose asChild> <Button type="submit" className="w-full"> Save changes </Button> </DialogClose> )}

this worked for me :)

@saifurrahmantanvir
Copy link

saifurrahmantanvir commented Mar 5, 2024

const [open, setOpen] = React.useState(false)

  const { reset } = form;
  const { isSubmitting, isSubmitSuccessful } = form.formState;

  React.useEffect(() => {
    isSubmitSuccessful && reset()

  }, [isSubmitSuccessful, reset])
<Dialog open={open} onOpenChange={setOpen}>

Dialog closing and form reset (react-hook-form) working properly with this. Didn't add anything like value={field.value} in the <Select onValueChange={field.onChange} defaultValue={field.value}> tag or anything in <SelectValue placeholder="Select priority" /> this tag.

@romhenri
Copy link

Very useful!

@lvbn
Copy link

lvbn commented Mar 26, 2024

This is how we can do, hope it helps

"use client";
// Imports here

export function AddProductModal() {
  const router = useRouter();
  const [open, setOpen] = useState(false);

  return (
    <Dialog open={open} onOpenChange={setOpen}>
      <DialogTrigger asChild>
        <Button
          className="bg-red-50 text-red-600"
        >
          Add Product
        </Button>
      </DialogTrigger>
      <DialogContent className="sm:max-w-[425px]">
         // ADD PRODUCT FORM HERE
        <DialogFooter>
          <Button
            onClick={() => {
              addProduct(data).then(() => setOpen(false));
              router.refresh();
            }}
            className="bg-red-50 text-red-600 hover:bg-red-100"
            type="submit"
          >
            Add
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

Does this only work on submit? I have been trying it in other parts of the code but it never works...

@SehajBindra
Copy link

This worked for me !!

"use client";
import React, { useEffect, useState } from "react";
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/Components/ui/dialog";
import { Button } from "./ui/button";
import Image from "next/image";
import Search from "./Search";
import { usePathname } from "next/navigation";

function SearchDialog() {
  const [open, setOpen] = useState(false);
  const pathname = usePathname();
  useEffect(() => {
    setOpen(false);
  }, [pathname]);
  return (
    <Dialog open={open} onOpenChange={setOpen}>
      <DialogTrigger className="z-10 cursor-pointer" asChild>
        <Image src="/search.svg" width={24} height={24} alt="search" />
      </DialogTrigger>
      <DialogContent className="rounded-xl">
        <DialogHeader>
          <DialogTitle>Share link</DialogTitle>
          <DialogDescription>
            Anyone who has this link will be able to view this.
            <DialogClose asChild>
              <Search />
            </DialogClose>
          </DialogDescription>
        </DialogHeader>
        <div className="flex items-center space-x-2"></div>
        <DialogFooter className="sm:justify-start">
          <DialogClose asChild>
            <Button type="button" variant="outline">
              Close
            </Button>
          </DialogClose>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

export default SearchDialog;import React, { useEffect, useState } from "react";
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/Components/ui/dialog";
import { Button } from "./ui/button";
import Image from "next/image";
import Search from "./Search";
import { usePathname } from "next/navigation";

function SearchDialog() {
  const [open, setOpen] = useState(false);
  const pathname = usePathname();
  useEffect(() => {
    setOpen(false);
  }, [pathname]);
  return (
    <Dialog open={open} onOpenChange={setOpen}>
      <DialogTrigger className="z-10 cursor-pointer" asChild>
        <Image src="/search.svg" width={24} height={24} alt="search" />
      </DialogTrigger>
      <DialogContent className="rounded-xl">
        <DialogHeader>
          <DialogTitle>Share link</DialogTitle>
          <DialogDescription>
            Anyone who has this link will be able to view this.
            <DialogClose asChild>
              <Search />
            </DialogClose>
          </DialogDescription>
        </DialogHeader>
        <div className="flex items-center space-x-2"></div>
        <DialogFooter className="sm:justify-start">
          <DialogClose asChild>
            <Button type="button" variant="outline">
              Close
            </Button>
          </DialogClose>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

export default SearchDialog;

@pnavk
Copy link

pnavk commented Apr 1, 2024

@OsamaQureshi147 For a basic requirement, you can probably use the searchParams and do a condition like:

/some-page?productId=69&modal=true

export default function Page({
  searchParams,
}: {
  searchParams: { [key: string]: string | string[] | undefined }
}) {
  const { productId, modal } = searchParams

  return (
    <div>
      <h1>Some Page</h1>
      <YourDialog open={modal === "true"} productId={productId} />
    </div>
  )
}

Then use <Link href="/some-page" /> to dismiss the modal.

Other than that, client component.

Demo:

Screen.Recording.2023-09-29.at.4.14.55.PM.mov

I had a slightly different requirement to be able to open a modal using the normal DialogTrigger but also from my navigation bar component which was in my root layout. I was able use this approach with some slight adjustments to get it to work. This preserves all the nice animations for opening and closing the modal while allowing the modal to be opened or closed from anywhere in the component tree.

Code is here in-case anyone is curious: https://github.com/pnavk/nextjs-rsc-modal-dialog-example

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests