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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 14:05:12