shadcn Dialog提交按钮不关闭问题及Next.js并行路由适配咨询
问题1:提交按钮无法关闭Dialog的解决方案
你的问题出在自定义DialogFooter的逻辑和提交按钮的绑定方式上,以下是修复步骤:
1. 调整主组件的Dialog结构,添加form标签
将输入框和提交按钮包裹在form中,利用shadcn Dialog的内置表单提交逻辑,同时控制提交按钮的禁用状态:
<Dialog> <DialogTrigger asChild> <Button variant="outline" className="rounded"> <AiOutlineUserAdd className="ml-2 h-4 w-4" /> Add Trainee </Button> </DialogTrigger> <DialogContent className="sm:max-w-[425px]"> <DialogHeader> <DialogTitle>Add Trainee</DialogTitle> <DialogDescription> Add the email of your trainee. </DialogDescription> </DialogHeader> {/* 添加form标签并绑定提交处理函数 */} <form onSubmit={handleSubmit} className="grid gap-4 py-4"> <div className="grid grid-cols-4 items-center gap-4"> <Label htmlFor="email" className="text-right"> Email </Label> <Input id="email" value={email} onChange={(e) => setEmail(e.target.value)} className="col-span-3 rounded" /> </div> <DialogFooter> {/* 根据邮箱验证结果禁用按钮,移除多余的DialogClose包裹 */} <Button type="submit" disabled={!isEmail}>Save</Button> {/* 保留取消按钮的DialogClose */} <DialogClose asChild> <Button variant="outline">Cancel</Button> </DialogClose> </DialogFooter> </form> </DialogContent> </Dialog>
2. 修改自定义DialogFooter组件
移除多余的DialogPrimitive.Close渲染,让组件回归基础的布局容器功能:
interface DialogFooterProps extends React.HTMLAttributes<HTMLDivElement> {} const DialogFooter: React.FC<DialogFooterProps> = ({ className, ...props }) => ( <div dir="rtl" className={cn( 'flex flex-col-reverse sm:flex-row sm:justify-center sm:space-x-2', className )} {...props} > {props.children} </div> ); DialogFooter.displayName = "DialogFooter"
3. 优化提交处理函数(可选)
如果表单提交后需要执行异步逻辑(比如API调用),可以手动调用Dialog的关闭函数,通过useDialogContext获取:
import { useDialogContext } from '@/components/ui/dialog' const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() // 执行你的提交逻辑,比如调用后端接口 await addTrainee(email) // 手动关闭Dialog const { close } = useDialogContext() close() // 重置邮箱输入 setEmail('') }
这样修改后,提交按钮在邮箱验证通过前处于禁用状态,提交后会自动关闭Dialog。
问题2:适配Next.js并行路由的方案
并行路由通过路由驱动Dialog的显示/隐藏,刷新页面时Dialog状态会保留,以下是实现步骤:
1. 创建并行路由目录结构
在你的主路由目录下创建@dialog文件夹,比如app/dashboard/@dialog/,并添加页面组件:
// app/dashboard/@dialog/page.tsx import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog' import { Button, Input, Label } from '@/components/ui' import { useRouter } from 'next/navigation' import { useState } from 'react' export default function AddTraineeDialog() { const router = useRouter() const [email, setEmail] = useState('') const emailCheck = (email: string) => { const re = /\S+@\S+\.\S+/ return re.test(email) } const isEmail = emailCheck(email) const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() // 执行提交逻辑 await addTrainee(email) // 关闭Dialog,返回原页面 router.back() } return ( <Dialog open> <DialogContent className="sm:max-w-[425px]"> <DialogHeader> <DialogTitle>Add Trainee</DialogTitle> <DialogDescription> Add the email of your trainee. </DialogDescription> </DialogHeader> <form onSubmit={handleSubmit} className="grid gap-4 py-4"> <div className="grid grid-cols-4 items-center gap-4"> <Label htmlFor="email" className="text-right"> Email </Label> <Input id="email" value={email} onChange={(e) => setEmail(e.target.value)} className="col-span-3 rounded" /> </div> <div className="flex justify-end gap-2"> <Button onClick={() => router.back()} variant="outline">Cancel</Button> <Button type="submit" disabled={!isEmail}>Save</Button> </div> </form> </DialogContent> </Dialog> ) } // 设置headers,标识该路由为模态框 export async function headers() { return { 'X-Dialog': 'true', } }
2. 在主页面中渲染并行路由Slot
在主页面(比如app/dashboard/page.tsx)中添加Slot组件,用于渲染并行路由的内容:
// app/dashboard/page.tsx import { Slot } from 'next/navigation' import { Button } from '@/components/ui' import { AiOutlineUserAdd } from 'react-icons/ai' import { useRouter } from 'next/navigation' export default function Dashboard() { const router = useRouter() const openDialog = () => { // 导航到包含并行路由的路径 router.push('/dashboard/@dialog') } return ( <div className="p-6"> <Button variant="outline" className="rounded" onClick={openDialog}> <AiOutlineUserAdd className="ml-2 h-4 w-4" /> Add Trainee </Button> {/* 渲染并行路由的Dialog */} <Slot name="dialog" /> </div> ) }
3. 可选:用中间件控制Dialog显示
如果需要根据URL参数控制Dialog显示,可以添加中间件:
// middleware.ts import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' export function middleware(request: NextRequest) { const url = request.nextUrl if (url.searchParams.has('add-trainee')) { // 重定向到并行路由路径 return NextResponse.redirect(new URL('/dashboard/@dialog', request.url)) } return NextResponse.next() } export const config = { matcher: ['/dashboard'], }
这样点击按钮时可以用router.push('/dashboard?add-trainee')来打开Dialog,更符合传统的参数传递方式。
内容的提问来源于stack exchange,提问作者steve123
相关产品推荐
相关产品推荐

