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

上传图片至Firestore后设置Formik字段值异常问题排查

问题:Formik表单提交时images字段返回undefined

场景与代码

我通过自定义Hook将文件上传至Firebase Storage,Hook代码如下:

import { storage } from "../firebase/config"
import { ref, getDownloadURL, uploadBytes } from "firebase/storage"

export const useUpload = () => {
    const upload = async (folder, file) => {
        const fileToUpload = ref(storage, `images/${folder}/${file.lastModified + file.name}`)
        const url = await uploadBytes(fileToUpload, file).then(async (snapshot) => {
            const imgURL = await getDownloadURL(snapshot.ref)
            return imgURL
        })
        return url;
    }

    return { upload }
}

在组件中导入并使用该Hook,同时用react-dropzone处理文件上传预览:

const {upload} = useUpload()
const previewFiles = (items) => {
        items.map(async (file) => {
            setIsLoading(true)
            await upload('propertyImages', file).then(url => {
                return Object.assign(file, { preview: url })
            })
            setIsLoading(false)
        })
        return items.map(({preview}) => preview)
    }

const { getRootProps, getInputProps } = useDropzone({
        accept: {
            'image/*': []
        },
        onDrop: acceptedFiles => {
            setFiles(previewFiles(acceptedFiles))
        }
    });

通过useEffect设置Formik字段值:

useEffect(() => {
        formik.setFieldValue("images", files.map(file => (file)))
        return () => files.forEach(file => URL.revokeObjectURL(file));
    }, [files]);

Formik配置:

const formik = useFormik({
        enableReinitialize: true,
        initialValues: {
            images: []
        },
        validationSchema: Yup.object({
            images: Yup.array().min(1, "Please upload at least one image").required()
        }),
        onSubmit: values => {
            console.log(values);
        }
    })

当前问题

提交Formik表单时,images字段返回undefined。尝试返回完整File对象而非仅图片URL时能正常工作:

// 修改后的previewFiles
const previewFiles = (items) => {
        items.map(async (file) => {
            setIsLoading(true)
            await upload('propertyImages', file).then(url => {
                return Object.assign(file, { preview: url })
            })
            setIsLoading(false)
        })
        return items
    }

// 修改后的useEffect
useEffect(() => {
        formik.setFieldValue("images", files.map(file => (file)))
        return () => files.forEach(file => URL.revokeObjectURL(file.preview));
    }, [files]);

但如果在useEffect中直接取file.preview赋值给Formik字段,就会返回undefined:

useEffect(() => {
        formik.setFieldValue("images", files.map(file => (file.preview)))
        return () => files.forEach(file => URL.revokeObjectURL(file));
    }, [files]);

问题原因

核心是异步操作时序错误:

  1. previewFiles里的items.map(async (file) => {})是异步操作,但你同步返回了items.map(({preview}) => preview)或items,此时上传请求还未完成,file.preview还未被赋值,返回的数组全是undefined。
  2. 返回完整items数组时,后续上传完成后file对象被修改(Object.assign),Formik引用的是同一个对象数组,所以后续能拿到preview;但直接返回preview数组时,初始返回的是undefined数组,异步完成后也不会更新这个数组,因为状态已经被设置。
  3. setIsLoading在map里多次调用会导致状态频繁更新,逻辑冗余。

解决方案

1. 修正异步逻辑,等待所有上传完成再返回结果

把previewFiles改成异步函数,用Promise.all批量处理上传,确保拿到所有结果后再返回:

const previewFiles = async (items) => {
    setIsLoading(true)
    // 批量处理所有文件上传,返回带preview的对象数组
    const processedFiles = await Promise.all(items.map(async (file) => {
        const url = await upload('propertyImages', file)
        return { ...file, preview: url }
    }))
    setIsLoading(false)
    return processedFiles
}

2. 修改onDrop的异步处理

因为previewFiles现在是异步函数,需要await后再更新状态:

const { getRootProps, getInputProps } = useDropzone({
    accept: {
        'image/*': []
    },
    onDrop: async acceptedFiles => {
        const processed = await previewFiles(acceptedFiles)
        setFiles(processed)
    }
});

3. 调整useEffect赋值逻辑

现在files数组中的每个元素都已包含preview字段,直接提取即可:

useEffect(() => {
    formik.setFieldValue("images", files.map(file => file.preview))
    return () => files.forEach(file => URL.revokeObjectURL(file.preview));
}, [files, formik]);

额外优化:简化useUpload代码

去除嵌套await,让代码更简洁:

export const useUpload = () => {
    const upload = async (folder, file) => {
        const fileRef = ref(storage, `images/${folder}/${file.lastModified + file.name}`)
        const snapshot = await uploadBytes(fileRef, file)
        const imgURL = await getDownloadURL(snapshot.ref)
        return imgURL
    }

    return { upload }
}

修改后,Formik的images字段会正确获取到上传后的URL数组,不会出现undefined的情况。

内容的提问来源于stack exchange,提问作者Awesome Bassey

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 19:01:14