React Hook Form + Yup 文件上传表单preview字段无法捕获文件值问题
问题分析
你的核心问题是React Hook Form 默认不会自动处理文件输入的状态同步——文件输入的value是只读的字符串,无法通过常规的register绑定直接获取选中的File对象,导致preview字段始终为undefined,验证自然失败。
解决方案
需要手动监听文件输入的onChange事件,通过React Hook Form的setValue方法将选中的文件同步到表单状态中,同时调整验证逻辑确保正确性。
步骤1:修改AdminFormInput组件
给文件输入添加自定义onChange事件,手动提取选中的文件并更新表单值:
// AdminFormInput 修改后 import { useRef } from 'react' import styles from './AdminFormInput.module.sass' import { FieldValues, Path, UseFormRegister, useFormContext } from 'react-hook-form' interface AdminFormInputProps<T extends FieldValues> { label: string isFile?: boolean previewText?: string isSelect?: boolean registerName: Path<T> register: UseFormRegister<T> isError: boolean errorText: string | undefined } export const AdminFormInput = <T extends FieldValues>({ label, isFile, previewText, isSelect, registerName, register, isError, errorText, }: AdminFormInputProps<T>) => { const fileRef = useRef<any>() // 从表单上下文获取setValue方法 const { setValue } = useFormContext() const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => { if (e.target.files && e.target.files[0]) { // 将选中的文件同步到表单状态,同时触发验证 setValue(registerName, e.target.files[0], { shouldValidate: true }) } } return ( <div className={styles.wrapper}> <label className={styles.title}>{label}</label> {!isFile && !isSelect && ( <> <input {...register(registerName)} type='text' className={styles.input} /> {isError && <span className={styles.error}>{errorText}</span>} </> )} {isFile && ( <> <input type='file' accept='image/png, image/gif, image/jpeg' hidden ref={fileRef} onChange={handleFileChange} /> <div className={styles.file} onClick={() => fileRef.current.click()}> {previewText} </div> {isError && <span className={styles.error}>{errorText}</span>} </> )} </div> ) } export default AdminFormInput
步骤2:调整AddCategory组件的验证规则
完善Yup验证规则,确保验证文件存在,还可按需添加类型/大小校验:
// AddCategory 中的验证规则修改 const validationSchema = Yup.object().shape({ name: Yup.string().required('Enter name'), preview: Yup.mixed() .required('You need to provide a file') // 可选:验证文件类型 .test('fileType', 'Only image files are allowed', (file) => { return file && ['image/png', 'image/gif', 'image/jpeg'].includes(file.type) }), })
步骤3:用FormProvider包裹表单(关键)
因为AdminFormInput中使用了useFormContext,需要在AddCategory组件中用FormProvider传递表单实例:
// AddCategory 修改后 import { FormProvider, useForm } from 'react-hook-form' import { yupResolver } from '@hookform/resolvers/yup' import * as Yup from 'yup' import styles from './AddCategory.module.sass' import AdminFormInput from './AdminFormInput' import AdminFormButton from './AdminFormButton' interface AddCategoryProps {} export default function AddCategory({}: AddCategoryProps) { const formMethods = useForm({ defaultValues: {}, mode: 'onChange', resolver: yupResolver(validationSchema), }) const { handleSubmit, formState: { errors } } = formMethods const onSubmit = handleSubmit(({ name, preview }) => { console.log({ name, preview }) }) return ( <section className={styles.wrapper}> <FormProvider {...formMethods}> <form onSubmit={onSubmit} className={styles.form}> <AdminFormInput register={formMethods.register} registerName='name' errorText={errors.name?.message} isError={Boolean(errors.name?.message)} label='Name' /> <AdminFormInput register={formMethods.register} registerName='preview' errorText={errors.preview?.message} isError={Boolean(errors.preview?.message)} label='Preview' isFile={true} previewText='Select file' /> <AdminFormButton /> </form> </FormProvider> </section> ) }
原代码无效的原因
- React Hook Form的
register方法默认绑定的onChange事件,只能获取到文件输入的字符串类型value(如C:\fakepath\image.jpg),无法直接拿到File对象。 - 必须手动监听
onChange事件,从e.target.files[0]中提取真实的File对象,再通过setValue同步到表单状态。
内容的提问来源于stack exchange,提问作者asd qwe
相关产品推荐
相关产品推荐

