React Hook Form+shadcn/ui表单提交无响应问题求助
React Hook Form + shadcn/ui 表单提交无响应,验证无提示
我在学校项目里用React Hook Form结合shadcn/ui开发表单,点击提交按钮时onSubmit完全没反应,连console.log都不输出。怀疑是验证问题,但添加错误提示后也没显示。相关代码和Schema如下:
import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Form, FormControl, FormField, FormItem, FormMessage } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { useFetch } from "@/hooks/fetcher"; import { AttendanceSchema } from "@/schemas/attendance-schemas"; import { zodResolver } from "@hookform/resolvers/zod"; import axios from "axios"; import React from "react"; import { useForm } from "react-hook-form"; import { useLocation } from "react-router-dom"; import { z } from "zod"; type StudentAttendanceType = { nisn: string; student_name: string; }; const statuses = [ "Hadir", "Izin", "Sakit", "Alfa" ]; const Presensi = () => { const [generatedId, setGeneratedId] = React.useState(""); const location = useLocation(); const scheduleId = location.pathname.split("/")[location.pathname.split("/").length - 1]; const { data } = useFetch<StudentAttendanceType[]>( "http://localhost:8800/backend/attendances/getToAttend/" + scheduleId ); const form = useForm<z.infer<typeof AttendanceSchema>>({ resolver: zodResolver(AttendanceSchema), defaultValues: { attendance_list_id: "123", attendance: [ { attendance_id: "123", status: "Hadir", student_id: "123", description: "", }, ], }, }); React.useEffect(() => { const fetchData = async () => { try { const res = await axios.get( "http://localhost:8800/backend/attendances/lastId/" + scheduleId ); setGeneratedId( parseInt(res.data[0].next_id).toString().padStart(3, "0") ); } catch (error) { console.log(error); } }; fetchData(); }, [generatedId, scheduleId]); React.useEffect(() => { form.reset({ attendance: data.map((student) => ({ status: "Hadir", student_id: student.student_name, description: "", })), }); }, [form, data]); const onSubmit = async (values: z.infer<typeof AttendanceSchema>) => { try { await axios.post("http://localhost:8800/backend/attendances/", { attendance_list_id: scheduleId + "-" + generatedId, schedule_id: scheduleId, attendance: data.map((student) => ({ attendance_id: student.nisn + generatedId, student_id: student.nisn, status: "Hadir", description: "" })), }); console.log(values); } catch (error) { console.log(error); } }; return ( <div className="flex flex-col h-full gap-6 overflow-y-hidden flex-nowrap whitespace-nowrap"> <h1 className="sm:text-3xl text-2xl font-bold leading-none text-neutral-900"> Presensi </h1> <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)}> <div className="flex flex-col justify-between gap-8 py-4 h-full overflow-y-auto"> {data .map((student, index) => ( <div key={student.nisn}> <FormField control={form.control} name={`attendance.${index}.student_id`} render={({ field }) => ( <FormItem className="grid w-full max-w-sm items-center gap-1.5"> <FormControl> <Input {...field} className="border-0" disabled /> </FormControl> <FormMessage/> </FormItem> )} /> <FormField control={form.control} name={`attendance.${index}.status`} render={({ field }) => ( <FormItem className=""> <Popover> <PopoverTrigger> <FormControl> <Badge className="w-[100px] flex justify-center"> {field.value ? field.value : "Select Status"} </Badge> </FormControl> <FormMessage/> </PopoverTrigger> <PopoverContent className="overflow-hidden p-0"> <ul className="text-center"> {statuses.map((status) => ( <li key={status} className="py-0.5 px-2 border-b" onClick={() => { form.setValue( `attendance.${index}.status`, status ); }} > {status} </li> ))} </ul> </PopoverContent> </Popover> </FormItem> )} /> </div> ))} <div className="flex justify-end gap-4"> <Button type="submit">Done</Button> </div> </div> </form> </Form> </div> ); }; export default Presensi;
Schema:
import { z } from "zod"; export const AttendanceSchema = z.object({ attendance_list_id: z.string().min(1, { message: "List ID is required!" }), attendance: z.array( z.object({ attendance_id: z.string().min(1, { message: "ID is required!" }), student_id: z.string().min(1, { message: "Student is required!" }), status: z.string().min(1, { message: "status is required!" }), description: z.optional(z.string().max(225, { message: "Maximum 255 characters!" })), }) ) });
问题分析与修复方案
核心问题
- 验证失败拦截提交:Schema要求
attendance数组每个对象必须包含attendance_id,但你在form.reset时完全没赋值,默认值也被覆盖,导致验证不通过,onSubmit无法触发。同时attendance_list_id在重置时被忽略,存在验证风险。 - 错误提示未绑定:
FormMessage没有关联对应字段的错误信息,导致验证失败后无提示。 - useEffect依赖错误:第一个
useEffect依赖generatedId会引发无限请求循环。 - 提交未使用表单真实值:当前提交逻辑直接用
data生成数据,忽略用户修改的状态。
修复步骤
1. 完善表单重置逻辑
在重置时补上所有必填字段:
React.useEffect(() => { if (!data) return; // 避免data为空时报错 form.reset({ attendance_list_id: scheduleId + "-" + generatedId, attendance: data.map((student) => ({ attendance_id: student.nisn + generatedId, status: "Hadir", student_id: student.student_name, description: "", })), }); }, [form, data, generatedId, scheduleId]);
2. 绑定错误提示到FormMessage
修改FormField的render函数,传递字段错误信息:
// student_id字段示例 <FormField control={form.control} name={`attendance.${index}.student_id`} render={({ field, fieldState }) => ( <FormItem className="grid w-full max-w-sm items-center gap-1.5"> <FormControl> <Input {...field} className="border-0" disabled /> </FormControl> <FormMessage>{fieldState.error?.message}</FormMessage> </FormItem> )} /> // status字段同理 <FormField control={form.control} name={`attendance.${index}.status`} render={({ field, fieldState }) => ( <FormItem className=""> <Popover> <PopoverTrigger> <FormControl> <Badge className="w-[100px] flex justify-center"> {field.value ? field.value : "Select Status"} </Badge> </FormControl> <FormMessage>{fieldState.error?.message}</FormMessage> </PopoverTrigger> {/* 其余代码不变 */} </Popover> </FormItem> )} />
3. 修正useEffect依赖
移除generatedId依赖,避免无限循环:
React.useEffect(() => { const fetchData = async () => { try { const res = await axios.get( "http://localhost:8800/backend/attendances/lastId/" + scheduleId ); setGeneratedId( parseInt(res.data[0].next_id).toString().padStart(3, "0") ); } catch (error) { console.log(error); } }; fetchData(); }, [scheduleId]);
4. 提交时使用表单真实值
改用values获取用户修改后的状态:
const onSubmit = async (values: z.infer<typeof AttendanceSchema>) => { try { await axios.post("http://localhost:8800/backend/attendances/", { attendance_list_id: values.attendance_list_id, schedule_id: scheduleId, attendance: values.attendance.map((item, index) => ({ ...item, attendance_id: data[index].nisn + generatedId, student_id: data[index].nisn // 替换为真实学生ID })), }); console.log(values); } catch (error) { console.log(error); } };
额外调试建议
- 添加空值判断避免报错:
{data?.map((student, index) => (...))} - 打印全局错误信息调试:
const { formState: { errors } } = form; console.log(errors);
内容的提问来源于stack exchange,提问作者Maliki Azis
相关产品推荐
相关产品推荐

