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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 17:53:14