使用 Radix Dialog 从另一个对话框处理对话框触发器

问题描述 投票:0回答:1

亲爱的前端爱好者。

我正在使用 Shadcn 组件的 NextJS Web 中工作,这是 Radix UI 之上的另一个高级层。我正在尝试构建一个页面构建器,它可以从 Strapi(CMS)动态加载组件并显示它们,因此代码将非常抽象。这就是为什么我偶然发现了一个奇怪的错误。

上下文:我有一个打开表单的按钮,其中有一个可以打开另一个表单的继续按钮。

预期:单击“继续”按钮后,当前对话框应关闭,并打开由“继续”按钮触发的下一个对话框。

发生了什么:单击“继续”按钮后,当前对话框关闭,然后下一个对话框打开,但随后立即关闭。

我是如何做到的:我向表单中的按钮传递一个triggerDialog,它应该是该按钮当前所在的父对话框的触发器。单击此按钮时,除了打开新对话框之外,还将触发其父对话框关闭它。

对话框

'use client'

import * as React from 'react'
import { useEffect } from 'react'
import { Button as ShadcnButton } from '@/components/ui/button'
import { buttonDialogVariants } from './button'
import { ButtonButtonComponent, FormResponseDataObject } from '@/api/generated'

import { cn } from '@/lib/utils'

import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog'

import { FormComponent } from '../form/form'
import { formSchema } from '../form/form'

export interface ButtonProps extends ButtonButtonComponent {
  asChild?: boolean
  className?: string
  formObject?: any
  triggerDialog?: any
}

export const ButtonDialog = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, size, asChild = false, formObject, type, ...props }, ref) => {
    const [open, setOpen] = React.useState(false)
    const submitType = type === 'submit' ? 'submit' : 'button'
    const selfID = React.useRef(Math.random().toString(36).substring(7)).current;

    useEffect(() => {
      console.log('button dialog on mount: ' + selfID + " state: " + open)
    }, [])

    const setOpenChange = (bool: boolean, msg?: string) => {
      if (props.form?.data?.attributes?.title === "Just one step to go") {
        setOpen(true)
      }
      if (formObject) {
        if (formSchema.safeParse(formObject.getValues()).success) {
          setOpen(bool)
        } else {
          setOpen(false)
        }
      } else {
        setOpen(bool)
      }
    }

    // this is a button to open another form and all data from this form
    // should be passed to the new form
    return (
      <Dialog open={open} onOpenChange={setOpenChange}>
        <DialogTrigger asChild={true}>
          <ShadcnButton
            type={submitType}
            className={cn(buttonDialogVariants({ variant, size, className }))}
            ref={ref}
          >
            {props.label}
          </ShadcnButton>
        </DialogTrigger>
        <DialogContent className='flex flex-row flex-wrap gap-4 rounded-xl'>
          {props.form?.data && (
            <FormComponent
              testingData={open}
              triggerDialog={setOpenChange}
              data={formObject?.getValues()}
              {...(props.form.data as FormResponseDataObject)}
              className='w-full'
            />
          )}
        </DialogContent>
      </Dialog>
    )
  }
)
ButtonDialog.displayName = 'ButtonDialog'

表格

'use client'

import * as React from 'react'
import { useEffect, useState } from 'react'
import { cn } from '@/lib/utils'
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import * as z from 'zod'
import { FormResponseDataObject } from '@/api/generated'
import { FormFieldComponent } from '@/components/form/fields/form-field'
import { Form } from '@/components/ui/form'
import { FormService } from '@/api/generated'
import { Title } from '../basic'
import { GroupOfButtons } from '../button/group-of-buttons'
import { DynamicZone } from '../dynamic-zone'

type Props = FormResponseDataObject & {
  className?: string
  data?: any
  triggerDialog?: any
  testingData?: any
}

export const formSchema = z
  .object({
    name: z.string({
      required_error: 'Please enter your name',
    }),
    email: z.string().email({
      message: 'Please enter a valid email',
    }),
    message: z.string(),
    dialCode: z.string(),
    phone: z
      .string()
      .min(9, {
        message: 'Your phone number must be at least 9 digits',
      })
      .max(10, {
        message: 'Your phone number must be at most 10 digits',
      }),
    field: z.any(),
    url: z.string().url(),
    file: z.any(),
    invoicesEstimation: z.any(),
    accountingSystem: z.any(),
  })
  .partial()

export const FormComponent = ({ id, className, data, ...props }: Props) => {
  const [formData, setFormData] = useState<FormResponseDataObject | undefined>(undefined)
  // generate a random id for debugging
  const selfID = React.useRef(Math.random().toString(36).substring(7)).current;

  useEffect(() => {
    const fetchFormStructure = async () => {
      const form = await FormService.getFormsId({ id, populate: 'fields,buttons.form' })
      setFormData(form.data)
    }

    fetchFormStructure()
    console.log('form component on mount: ' + selfID)
    console.log(props.testingData)
  }, [id])

  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: data,
  })

  const onSubmit = async (data: z.infer<typeof formSchema>) => {
    console.log('Form submitted ', selfID)
    console.log(data)
    props.triggerDialog?.(false, "fudge you")
  }

  return (
    <div className={cn(className, 'flex flex-row')}>
      <div className='flex flex-col gap-8 w-full'>
        <div className='flex flex-col'>
          {formData?.attributes?.title && <Title title={formData.attributes.title} size='h3' />}
          {formData?.attributes?.description && (
            <p className='text-start md:text-center text-grey-600'>{formData.attributes.description}</p>
          )}
        </div>
        <Form {...form}>
          <form onSubmit={form.handleSubmit(onSubmit)} className='flex flex-col gap-4'>
            {formData?.attributes?.fields?.map(field => {
              if (!field) {
                return null
              }

              const { id, ...props } = field

              return <FormFieldComponent key={id} form={form} {...props} />
            })}
            <GroupOfButtons
              triggerDialog={props.triggerDialog}
              formObject={form}
              className='mt-10 w-full'
              buttons={formData?.attributes?.buttons}
            />
          </form>
        </Form>
        {formData?.attributes?.media && <DynamicZone components={formData.attributes.media} />}
      </div>
    </div>
  )
}

表单中的按钮组(加载按钮对话框)

'use client'

import { cn } from '@/lib/utils'
import { Button } from './button'
import { ButtonVideo } from './button-video'
import { ButtonDialog } from './button-dialog'

import { ButtonGroupOfButtonsComponent } from '@/api/generated'

type Props = ButtonGroupOfButtonsComponent & {
  className?: string
  triggerDialog?: any
  formObject?: any
}

export const GroupOfButtons = ({ className, buttons, formObject, ...props }: Props) => {
  return (
    <div className={cn('flex flex-wrap justify-start gap-4 md:justify-between md:gap-8', className)}>
      {buttons?.map((button, index) => {
        if (!button) {
          return null
        }
        if (button.form?.data) {
          return (
            <ButtonDialog
              formObject={formObject}
              key={index}
              // triggerDialog={props.triggerDialog}
              {...button}
            />
          )
        }
        if (button.link?.includes('youtube')) {
          return <ButtonVideo key={index} {...button} />
        }
        return (
          <Button key={index} {...button}>
            {button.label}
          </Button>
        )
      })}
    </div>
  )
}

在记录控制台日志以进行调试时,我没有注意到第二个对话框根本会关闭,但它确实会关闭。

除了传递父对话框的触发器(也许是一些对话框上下文)之外,也许您对如何实现这一点有更好的想法......?但我不想让它变得复杂,我想知道为什么这个方法一开始不起作用。

非常感谢!

我尝试记录日志以进行调试。我尝试查看 Radix UI 的源代码,发现它们使用了一些对话框上下文,但具有正确的引用,因此它不应关闭所有打开的对话框。

next.js frontend dialog radix-ui
1个回答
0
投票

我找到了答案: 该问题是事件传播处理不当。我遇到了同样的问题,发现解决方案是将

onClick={(e) => e.stopPropagation()}
添加到每个
<DialogOverlay />
(如果还没有,您需要将其添加到您的
ButtonDialog
组件中)、
<DialogTrigger>
<DialogContent> 
元素。但仅限于内部对话框(您的
ButtonDialog
)。

© www.soinside.com 2019 - 2024. All rights reserved.