React中Formik+Dropzone上传文件接口返回空对象问题
React+Formik+Dropzone文件上传为空问题修复
问题复现
大型表单普通字段提交正常,文件字段提交后为空对象,二进制文件未正确传输,请求效果截图:
核心错误原因
- 请求格式错误:直接将
FormData实例包裹在普通JS对象中传给axios,axios默认会对普通对象做JSON序列化,FormData序列化后为空对象,无法携带二进制数据;同时未按multipart/form-data格式的要求让浏览器自动生成正确的请求头。 - 状态源不统一:Dropzone组件使用react-dropzone内部维护的
acceptedFiles渲染文件列表,和Formik管理的表单状态脱节,容易出现选中文件后Formik实际未拿到正确值的问题。 - 无效字段冗余:构造JSON提交数据时未剔除file字段,二进制文件无法被JSON序列化,属于无效传输内容。
- 初始值类型不匹配:Formik中file初始值设为
null,和Dropzone返回的数组类型不一致,容易引发取值报错。
修复方案
1. 修正提交逻辑
所有请求字段统一放入同一个FormData实例,不要拆分普通对象和FormData传输;请求时直接传FormData实例,让浏览器自动设置带正确boundary的Content-Type头,禁止手动设置multipart/form-data头(手动设置会丢失boundary参数导致后端解析失败)。
2. 统一状态源
Dropzone组件接收Formik传入的file值作为文件列表的渲染数据源,不要依赖组件内部的acceptedFiles状态,保证表单状态唯一。
3. 修正后完整代码
Formik配置与提交逻辑
<Formik initialValues={{ name: "", // 其余表单字段 file: [], // file初始值改为空数组,匹配Dropzone返回的数组类型 note: "", }} onSubmit={async (values) => { // 先从表单值中剔除file字段,避免混入JSON序列化逻辑 const { file, ...restFormValues } = values; // 构造需要JSON序列化的字段 const jsonData = { ...restFormValues, address: JSON.stringify(values.address), products: JSON.stringify(values.products), }; const formData = new FormData(); // 将JSON数据转为Blob类型追加到FormData,指定正确MIME类型 formData.append( "jsonData", new Blob([JSON.stringify(jsonData)], { type: "application/json" }) ); // 追加选中的文件,做空值判断避免报错 if (file.length > 0) { // 单文件上传取第一个即可,多文件可遍历append formData.append("file", file[0]); // 多文件上传用下面的写法 // file.forEach(singleFile => formData.append("file", singleFile)); } // 直接传入formData实例,不要套外层普通对象 await axios.post("https://nehra.az/api/add-supplier", formData); }} > {({ setFieldValue, values }) => ( <Form> {/* 其余普通表单项 */} <div className="form-input"> {/* 传入Formik的setFieldValue和当前file值给Dropzone组件 */} <Dropzone setFieldValue={setFieldValue} fileValue={values.file} /> <span className="error-span"> <ErrorMessage name="file" /> </span> </div> <button type="submit">提交</button> </Form> )} </Formik>
Dropzone子组件修正
const Dropzone = ({ setFieldValue, fileValue = [] }) => { const { getRootProps, getInputProps, isFocused, isDragAccept, isDragReject } = useDropzone({ onDrop: (acceptedFiles) => { // 选中文件后同步到Formik状态 setFieldValue("file", acceptedFiles); }, }); // 统一使用Formik传入的fileValue渲染文件列表,保证状态同步 const fileList = fileValue.map((file) => ( <li key={file.path}>{file.path}</li> )); return ( <div className="container"> <div {...getRootProps({ style })}> <input {...getInputProps()} multiple /> <p>点击选择文件或将文件拖拽到此处</p> {fileList.length > 0 ? <ul>{fileList}</ul> : null} </div> </div> ); };
提示:如果后端接口要求普通表单字段和文件字段平级接收,不需要嵌套
jsonData字段,直接将每个普通字段单独append到FormData实例即可,比如formData.append("name", values.name)、formData.append("address", JSON.stringify(values.address)),根据后端实际参数要求调整即可。
内容的提问来源于stack exchange,提问作者Rahil Əliyev
相关产品推荐
相关产品推荐

