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
相关产品推荐
相关产品推荐

