Next.js & ShadCN:注册表单提交按钮无响应问题求助
注册弹窗提交按钮无响应问题排查与解决
我用Next.js + ShadCN开发的注册弹窗,点击type="submit"的提交按钮完全没反应。绑定的handleSignup函数里有console.log,本该打印注册信息;调用的后端接口也该输出成功/失败日志,但都没触发。已经排查过onOpenChange和onClose方法,没找到问题。
核心排查点与修复方案
1. 表单校验未通过(最常见原因)
你的Zod校验规则非常严格,只要有一项不满足,React Hook Form就会阻止表单提交,且不会进入handleSignup函数。可以通过以下方式确认:
- 检查页面是否有红色的校验错误提示(你已实现错误提示UI)
- 在组件中添加表单合法性状态打印:
const { register, handleSubmit, formState: { errors, isValid }, } = useForm<SignupInput>({ resolver: zodResolver(signupSchema), }); // 打印当前表单是否合法 console.log("表单合法性状态:", isValid);
2. 修复Dialog事件拦截问题
ShadCN的Dialog组件可能会拦截表单提交事件,手动处理提交逻辑:
<form onSubmit={(e) => { e.preventDefault(); handleSubmit(handleSignup)(); }} className="flex flex-col gap-2" >
3. 修正第二个Button的type属性
表单内的"Sign Up with Google"按钮未指定type="button",默认会被识别为submit类型,可能导致事件冲突。给它添加type属性:
<Button type="button">Sign Up with Google</Button>
4. 给异步函数添加错误捕获
handleSignup是异步函数,未添加错误捕获可能导致静默失败,添加try-catch块:
const handleSignup = async (data: SignupInput) => { try { console.log("Signup data:", data); const response = await fetch(`http://localhost:4000/api/auth/signup`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }); if (response.ok) { const resData = await response.json(); console.log("Signup successful:", resData); } else { console.error("Signup failed"); } } catch (err) { console.error("Signup error:", err); } };
完整组件代码
import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { useState } from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { SignupInput, signupSchema } from "@/lib/schemas"; import { Eye, EyeOff } from "lucide-react"; import { Checkbox } from "@/components/ui/checkbox"; import { Separator } from "@/components/ui/separator"; interface SignupDialogProps { isOpen: boolean; onClose: () => void; } export function SignupDialog({ isOpen, onClose }: SignupDialogProps) { const [showPassword, setShowPassword] = useState(false); const { register, handleSubmit, formState: { errors, isValid }, } = useForm<SignupInput>({ resolver: zodResolver(signupSchema), }); console.log("表单合法性状态:", isValid); const handleSignup = async (data: SignupInput) => { try { console.log("Signup data:", data); const response = await fetch(`http://localhost:4000/api/auth/signup`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }); if (response.ok) { const resData = await response.json(); console.log("Signup successful:", resData); } else { console.error("Signup failed"); } } catch (err) { console.error("Signup error:", err); } }; return ( <Dialog open={isOpen} onOpenChange={onClose}> <DialogContent className="h-[75%]"> <DialogHeader> <DialogTitle className="text-white pb-6">{""}</DialogTitle> <DialogDescription className="text-2xl font-semibold text-white"> Create Account </DialogDescription> <DialogDescription className="text-white"> Quickly signup for an account now </DialogDescription> </DialogHeader> <form onSubmit={(e) => { e.preventDefault(); handleSubmit(handleSignup)(); }} className="flex flex-col gap-2" > <div> <label htmlFor="fullName" className="text-white"> Full Name </label> <Input className={`text-gray-400 ${errors.fullName && "border-red-500"}`} type="text" placeholder="Eg. John Doe" {...register("fullName")} /> {errors.fullName?.message && ( <p className="text-red-500 text-sm ps-1 pt-1"> {String(errors.fullName.message)} </p> )} </div> <div> <label htmlFor="email" className="text-white"> Email </label> <Input className={`text-gray-400 ${errors.email && "border-red-500"}`} type="email" placeholder="Eg. johndoe@gmail.com" {...register("email")} /> {errors.email?.message && ( <p className="text-red-500 text-sm ps-1 pt-1"> {String(errors.email.message)} </p> )} </div> <div> <label htmlFor="password" className="text-white"> Password </label> <div className="relative"> <Input className={`text-gray-400 ${ errors.password && "border-red-500" }`} type={showPassword ? "text" : "password"} placeholder="Eg. X92b@!k32" {...register("password")} /> <Button type="button" variant="ghost" size="icon" className="absolute right-2 top-1/2 transform -translate-y-1/2" onClick={() => setShowPassword(!showPassword)} > {showPassword ? ( <EyeOff className="text-white h-4 w-4" /> ) : ( <Eye className="text-white h-4 w-4" /> )} </Button> </div> {errors.password?.message && ( <p className="text-red-500 text-sm ps-1 pt-1"> {String(errors.password.message)} </p> )} </div> <div className="flex items-center space-x-2 mb-4 mt-2"> <Checkbox id="keepLoggedIn" {...register("keepLoggedIn")} /> <label htmlFor="keepLoggedIn" className="text-sm text-white leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" > Keep me logged in </label> </div> <Button type="submit">Sign Up</Button> <Separator className="my-3" /> <Button type="button">Sign Up with Google</Button> </form> </DialogContent> </Dialog> ); }
校验Schema代码
import { z } from "zod"; export const signupSchema = z .object({ fullName: z.string().min(2, "Full name is required"), email: z.string().min(1, "Email is required").email("Invalid email address"), password: z .string() .min(8, "Password must be at least 8 characters long") .regex(/[A-Z]/, "Password must contain at least one uppercase letter") .regex(/[a-z]/, "Password must contain at least one lowercase letter") .regex(/[0-9]/, "Password must contain at least one number") .refine( (password) => !["password", "password123"].includes(password), "Password is too weak" ), keepLoggedIn: z.boolean().optional(), }) .refine( (data) => !data.password.includes(data.email.split("@")[0]), "Password should not contain your email" ); export type SignupInput = z.infer<typeof signupSchema>;
内容的提问来源于stack exchange,提问作者Alula48
相关产品推荐
相关产品推荐

