使用ZodResolver时React Hook Form保存按钮禁用且无法提交的问题
问题解决:React Hook Form + Zod 表单验证导致保存按钮禁用
核心问题分析及修复步骤
1. 移除useForm多余的依赖数组
你的useForm调用多传了一个空数组[],React Hook Form的useForm只接受一个配置对象参数,多余的数组会导致表单初始化异常,状态更新逻辑出错。
修改前:
const form = useForm({ resolver: zodResolver(priceSchema), defaultValues: course }, []);
修改后:
const form = useForm({ resolver: zodResolver(priceSchema), defaultValues: course });
2. 解决输入值类型不匹配问题
HTML的number输入框实际返回的是字符串类型,但你的Zod schema要求price是number类型,这会导致验证一直不通过,isValid始终为false。
需要在register里添加valueAsNumber转换,确保输入值被转为数字:
修改input的register部分:
<input className='p-1 shadow-lg appearance-none rounded w-full outline-none' type="number" id='price' placeholder="Enter course selling price..." disabled={isSubmitting} {...form.register('price', { valueAsNumber: true })} />
注意:移除input上的defaultValue,因为useForm的defaultValues已经会自动设置初始值,重复设置会导致状态不一致。
3. 修正表单提交按钮的位置和绑定
你的保存按钮不在<form>标签内部,且用了onClick绑定handleSubmit,这不符合表单提交的标准逻辑,容易导致验证流程失效。
把编辑状态下的按钮组移到<form>内部,直接利用form的onSubmit事件:
修改后的JSX结构:
{isEditing ? ( <form className='mt-2' onSubmit={handleSubmit(onSubmit)}> <input className='p-1 shadow-lg appearance-none rounded w-full outline-none' type="number" id='price' placeholder="Enter course selling price..." disabled={isSubmitting} {...form.register('price', { valueAsNumber: true })} /> {/* 把按钮移到form内部 */} <div className='flex gap-x-2 mt-2'> <button className='px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-md' type="button" onClick={() => { setIsEditing(false) reset(course) }}> Cancel </button> <button type='submit' disabled={!isValid || isSubmitting} className='px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-md'> Save </button> </div> </form> ) : ( <p className='text-md font-semibold mt-2 py-1'> {course.price || 0} </p> )}
同时移除原来在form外的按钮组,确保submit按钮在form内,type为submit,不需要额外绑定onClick。
4. 验证初始值的类型
确保course.price是数字类型,如果后端返回的是字符串,需要在设置defaultValues时转换:
const form = useForm({ resolver: zodResolver(priceSchema), defaultValues: { ...course, price: Number(course.price) || 0 } });
完整修复后的代码片段
import React, { useState } from 'react' import { z } from 'zod' import { zodResolver } from '@hookform/resolvers/zod' import { useForm } from 'react-hook-form' const priceSchema = z.object({ price: z.number().positive().int() }) const PriceForm = ({ course, courseId, fetchData }) => { const [isEditing, setIsEditing] = useState(false); // 修复useForm的参数,转换初始值类型 const form = useForm({ resolver: zodResolver(priceSchema), defaultValues: { ...course, price: Number(course.price) || 0 } }); const { handleSubmit, reset } = form; const { isSubmitting, isValid } = form.formState; const onSubmit = async (values) => { try { setIsEditing(false); await fetch(`http://localhost:3000/instructor/courses/${courseId}`, { method: 'PATCH', headers: { authorization: `Bearer ${localStorage.getItem('token')}`, 'content-type': 'application/json' }, body: JSON.stringify(values) }); fetchData(); } catch (error) { console.log(error); } } return ( <div className='mt-6 border bg-slate-200 rounded-md p-4'> <div className='font-medium flex items-center justify-between'> <p className='text-zinc-600 py-2'>Course Price</p> {!isEditing && ( <button onClick={() => setIsEditing(true)}> 🖋 Edit </button> )} </div> {isEditing ? ( <form className='mt-2' onSubmit={handleSubmit(onSubmit)}> <input className='p-1 shadow-lg appearance-none rounded w-full outline-none' type="number" id='price' placeholder="Enter course selling price..." disabled={isSubmitting} {...form.register('price', { valueAsNumber: true })} /> <div className='flex gap-x-2 mt-2'> <button className='px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-md' type="button" onClick={() => { setIsEditing(false) reset({ ...course, price: Number(course.price) || 0 }) }}> Cancel </button> <button type='submit' disabled={!isValid || isSubmitting} className='px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-md'> Save </button> </div> </form> ) : ( <p className='text-md font-semibold mt-2 py-1'> {course.price || 0} </p> )} </div> ) } export default PriceForm
内容的提问来源于stack exchange,提问作者Prateek Kumar
相关产品推荐
相关产品推荐

