You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NextJS中Shadcn/Radix Dialog嵌套切换异常求助

解决Shadcn/Radix UI对话框切换时自动关闭的问题

问题场景

基于NextJS+Shadcn(Radix UI上层组件库)开发页面构建器,从Strapi动态加载组件。点击表单内的「Continue」按钮,预期关闭当前对话框并打开新对话框,但实际新对话框打开后立即关闭,控制台无关闭操作日志。

核心原因

新的ButtonDialog组件渲染在父对话框的DialogContent内部,当父对话框关闭时,该组件会随父对话框内容一同被卸载,导致其内部维护的open状态被销毁,Radix UI的Dialog因组件卸载自动关闭。

解决方案

将新对话框的状态提升到父对话框之外的层级(如页面组件),避免组件卸载导致状态丢失;同时调整触发逻辑,确保父对话框关闭后再打开新对话框。

步骤1:页面层级维护对话框状态

在页面组件中添加状态管理当前要打开的表单ID:

'use client';
import { useState } from 'react';
import { ButtonDialog } from '@/components/button/button-dialog';

export default function Page() {
  const [currentOpenFormId, setCurrentOpenFormId] = useState<string | null>(null);

  // 暴露给子组件的方法,用于设置要打开的新表单
  const handleSetOpenFormId = (id: string) => {
    setCurrentOpenFormId(id);
  };

  return (
    <div>
      {/* 原有页面内容,将方法传递给初始的表单/对话框组件 */}
      <YourInitialComponent setOpenFormId={handleSetOpenFormId} />
      
      {/* 渲染动态对话框 */}
      {currentOpenFormId && (
        <ButtonDialog 
          form={{ data: { id: currentOpenFormId } }}
          onClose={() => setCurrentOpenFormId(null)}
          label="Next Form"
        />
      )}
    </div>
  );
}

步骤2:修改表单提交逻辑

在FormComponent的onSubmit方法中,先传递下一个表单ID,再关闭当前对话框:

const onSubmit = async (data: z.infer<typeof formSchema>) => {
  console.log('Form submitted ', selfID);
  console.log(data);
  
  // 获取Continue按钮对应的下一个表单ID
  const nextButton = formData?.attributes?.buttons?.find(btn => btn.label === 'Continue');
  const nextFormId = nextButton?.form?.data?.id;
  
  if (nextFormId && props.setOpenFormId) {
    // 通知页面打开新表单
    props.setOpenFormId(nextFormId);
  }
  
  // 关闭当前对话框
  props.triggerDialog?.(false);
};

步骤3:调整ButtonDialog组件

移除硬编码的状态强制设置逻辑,改为支持外部触发打开:

export const ButtonDialog = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, size, asChild = false, formObject, type, onClose, ...props }, ref) => {
    const [open, setOpen] = React.useState(false);

    const setOpenChange = (bool: boolean) => {
      setOpen(bool);
      if (!bool) {
        onClose?.();
      }
      // 保留表单验证逻辑(仅在需要时触发)
      if (formObject) {
        if (formSchema.safeParse(formObject.getValues()).success) {
          setOpen(bool);
        } else {
          setOpen(false);
        }
      }
    };

    // 组件挂载时自动打开(如果是页面级触发)
    React.useEffect(() => {
      if (props.form?.data) {
        setOpen(true);
      }
    }, [props.form?.data]);

    return (
      <Dialog open={open} onOpenChange={setOpenChange}>
        {/* 仅在需要手动触发时显示按钮 */}
        {!props.form?.data && (
          <DialogTrigger asChild={true}>
            <ShadcnButton
              type={type === 'submit' ? 'submit' : 'button'}
              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
              triggerDialog={setOpenChange}
              setOpenFormId={props.setOpenFormId}
              data={formObject?.getValues()}
              {...(props.form.data as FormResponseDataObject)}
              className='w-full'
            />
          )}
        </DialogContent>
      </Dialog>
    );
  }
);
ButtonDialog.displayName = 'ButtonDialog';

关键优化:移除硬编码逻辑

删除setOpenChange中硬编码的标题判断,避免逻辑冲突:

// 移除以下代码
// if (props.form?.data?.attributes?.title === "Just one step to go") {
//   setOpen(true)
// }

代码说明

  • 页面层级状态currentOpenFormId确保新对话框不受父组件卸载影响;
  • 表单提交时先传递下一个表单ID,再关闭当前对话框,避免组件卸载导致新对话框状态丢失;
  • 调整ButtonDialog的自动打开逻辑,适配页面级触发场景。

内容的提问来源于stack exchange,提问作者Han

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 05:40:55