Next.js Discord

Discord Forum

RadixUI Dialog with router.refresh() cause a flicker

Unanswered
PepeW posted this in #help-forum
Open in Discord
I have a RadixUI Dialog inside of which I have a form. When my form submission AND the router.refresh() are done, I'm closing the modal.
It's working as expected except for one thing: After the router.refresh(), the modal close then re-open then re-close very fast causing a flicker.

Here's the code:
"use client"
export function ChangeAddressForm({ addresses, setOpenModal }: ChangeAddressFormProps) {
  const [isLoading, setIsLoading] = useState(false)
  const [isSubmitDone, setIsSubmitDone] = useState(false)

  const router = useRouter()
  const [isPending, startTransition] = useTransition()

  useEffect(() => {
    if (isSubmitDone && !isPending) {
      if (setOpenModal) setOpenModal(false)
    }
  }, [isPending, isSubmitDone, setOpenModal])

async function onSubmit(formValues: z.infer<typeof formSchema>) {
    setIsLoading(true)
    try {
      await updateAddress.mutateAsync(formValues.newAddress)
      startTransition(() => {
        router.refresh()
      })
      setIsLoading(false)
      setIsSubmitDone(true)
    } catch {
      setIsLoading(false)
      return
    }
    setIsLoading(false)
  }

  return (
    <>
      <Form {...form}>
        <form onSubmit={form.handleSubmit(onSubmit)}>
          <FormField
            control={form.control}
            name="id"
            render={({ field }) => (
              <FormItem>
                <FormControl>
                  <RadioGroup onValueChange={field.onChange} defaultValue={field.value}>
                    {addresses.items.map((address) => (
                      ...
                    ))}
                  </RadioGroup>
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          <Button type="submit" isLoading={isLoading || isPending}>
            Choose this address
          </Button>
        </form>
      </Form>
    </>
  )
}

27 Replies

@PepeW I have a RadixUI Dialog inside of which I have a form. When my form submission AND the `router.refresh()` are done, I'm closing the modal. It's working as expected except for one thing: After the `router.refresh()`, the modal close then re-open then re-close very fast causing a flicker. Here's the code: javascript "use client" export function ChangeAddressForm({ addresses, setOpenModal }: ChangeAddressFormProps) { const [isLoading, setIsLoading] = useState(false) const [isSubmitDone, setIsSubmitDone] = useState(false) const router = useRouter() const [isPending, startTransition] = useTransition() useEffect(() => { if (isSubmitDone && !isPending) { if (setOpenModal) setOpenModal(false) } }, [isPending, isSubmitDone, setOpenModal]) async function onSubmit(formValues: z.infer<typeof formSchema>) { setIsLoading(true) try { await updateAddress.mutateAsync(formValues.newAddress) startTransition(() => { router.refresh() }) setIsLoading(false) setIsSubmitDone(true) } catch { setIsLoading(false) return } setIsLoading(false) } return ( <> <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)}> <FormField control={form.control} name="id" render={({ field }) => ( <FormItem> <FormControl> <RadioGroup onValueChange={field.onChange} defaultValue={field.value}> {addresses.items.map((address) => ( ... ))} </RadioGroup> </FormControl> <FormMessage /> </FormItem> )} /> <Button type="submit" isLoading={isLoading || isPending}> Choose this address </Button> </form> </Form> </> ) }
try comment out this
// useEffect(() => {
//    if (isSubmitDone && !isPending) {
//      if (setOpenModal) setOpenModal(false)
//    }
//  }, [isPending, isSubmitDone, setOpenModal])
If I do this the modal never closes by itself after the refresh is done because I'm not using setOpenModal(false) anywhere in my component
the modal should close after router.refresh()?
or not?
Yes the modal should close after the router.refresh()
so you don't need to call setOpenModal(false), right?
As stated is the NextJS doc, when doing a router.refresh(), the states of the client components are not touched, rsulting is the modal staying open even after the router.refresh()

router.refresh(): Refresh the current route. Making a new request to the server, re-fetching data requests, and re-rendering Server Components. The client will merge the updated React Server Component payload without losing unaffected client-side React (e.g. useState) or browser state (e.g. scroll position).
could you show the code where the modal state is?
"use client"
export function ModalChangeAddress({ addresses }: ModalChangeAddressProps) {
  const [openModal, setOpenModal] = useState(false)

  return (
    <Dialog open={openModal} onOpenChange={setOpenModal}>
      <DialogTrigger asChild>
        <Button>Change address</Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>
            <span>My addresses</span>
            <DialogClose>
              <IoClose />
            </DialogClose>
          </DialogTitle>
          <DialogDescription asChild>
              <ChangeAddressForm
                addresses={addresses}
                setOpenModal={setOpenModal}
              />
          </DialogDescription>
        </DialogHeader>
      </DialogContent>
    </Dialog>
  )
}
I know you need isPending for the UI but try with it first
If I don't call router.refresh(), the data on the page will not be refreshed with the new data I just submitted
async function onSubmit(formValues: z.infer<typeof formSchema>) {
    setIsLoading(true)
    try {
      await updateAddress.mutateAsync(formValues.newAddress)
      router.refresh()
      setIsLoading(false)
      setOpenModal(false) // need this to close the modal
    } catch {
      setIsLoading(false)
      return
    }
    setIsLoading(false)
  }


Do you mean like this ?
"use client"
export function ChangeAddressForm({ addresses, setOpenModal }: ChangeAddressFormProps) {
  const [isLoading, setIsLoading] = useState(false)
  const [isSubmitDone, setIsSubmitDone] = useState(false)

  const router = useRouter()
  const [isPending, startTransition] = useTransition()

  useEffect(() => {
    if (isSubmitDone && !isPending) {
      if (setOpenModal) setOpenModal(false)
    }
  }, [isPending, isSubmitDone, setOpenModal])

async function onSubmit(formValues: z.infer<typeof formSchema>) {
    setIsLoading(true)
    try {
      await updateAddress.mutateAsync(formValues.newAddress)
      //startTransition(() => {
        router.refresh()
      //})
      setIsLoading(false)
      setIsSubmitDone(true)
    } catch {
      setIsLoading(false)
      return
    }
    setIsLoading(false)
  }

  return (
    <>
      <Form {...form}>
        <form onSubmit={form.handleSubmit(onSubmit)}>
          <FormField
            control={form.control}
            name="id"
            render={({ field }) => (
              <FormItem>
                <FormControl>
                  <RadioGroup onValueChange={field.onChange} defaultValue={field.value}>
                    {addresses.items.map((address) => (
                      ...
                    ))}
                  </RadioGroup>
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          <Button type="submit" isLoading={isLoading || isPending}>
            Choose this address
          </Button>
        </form>
      </Form>
    </>
  )
}

like this
Ok so the modal closes after the submission but before the router.refresh()
So the modal closes, then I can see my previous address, then the refresh() is done, then my new address is displayed
I'm not sure to understand.
- If I don't call setOpenModal(false), the modal never closes by itslef => Not what I want
- If I do what you just show (basically not using startTransition()), the modal closes after submission but before router.refresh(), resulting in the user seeing the ui change "magically" => Not what I want
- If I use startTransition(), the modal closes after the router.refresh() (good) but somehow I have this weird flickering of the modal => Not what I want
@PepeW I'm not sure to understand. - If I don't call `setOpenModal(false)`, the modal never closes by itslef => Not what I want - If I do what you just show (basically not using `startTransition()`), the modal closes after submission but before router.refresh(), resulting in the user seeing the ui change "magically" => Not what I want - If I use `startTransition()`, the modal closes after the `router.refresh()` (good) but somehow I have this weird flickering of the modal => Not what I want
I mean this
"use client"
export function ChangeAddressForm({ addresses, setOpenModal }: ChangeAddressFormProps) {
  const [isLoading, setIsLoading] = useState(false)
  const [isSubmitDone, setIsSubmitDone] = useState(false)

  const router = useRouter()
  const [isPending, startTransition] = useTransition()

 // useEffect(() => {
 //   if (isSubmitDone && !isPending) {
 //     if (setOpenModal) setOpenModal(false)
 //   }
 // }, [isPending, isSubmitDone, setOpenModal])

async function onSubmit(formValues: z.infer<typeof formSchema>) {
    setIsLoading(true)
    try {
      await updateAddress.mutateAsync(formValues.newAddress)
      startTransition(() => {
        router.refresh()
      })
      setIsLoading(false)
      setIsSubmitDone(true)
    } catch {
      setIsLoading(false)
      return
    }
    setIsLoading(false)
  }

  return (
    <>
      <Form {...form}>
        <form onSubmit={form.handleSubmit(onSubmit)}>
          <FormField
            control={form.control}
            name="id"
            render={({ field }) => (
              <FormItem>
                <FormControl>
                  <RadioGroup onValueChange={field.onChange} defaultValue={field.value}>
                    {addresses.items.map((address) => (
                      ...
                    ))}
                  </RadioGroup>
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          <Button type="submit" isLoading={isLoading || isPending}>
            Choose this address
          </Button>
        </form>
      </Form>
    </>
  )
}
does the modal close?
No :/
@PepeW No :/
oh well, could you use server action instead of router.refresh?
I've not used them in my app yet, maybe it's the right moment ^^
@PepeW I've not used them in my app yet, maybe it's the right moment ^^
yeah try it, you could use revalidatePath with server action so you don't need to use router.refresh
Ok I'll give it a go, thank you !