React图片上传组件对接API遇422错误:文件需嵌套请求体
Hey there! I see you're stuck on getting your file upload to work with that nested request structure your backend expects. Let's break down what's going wrong and fix it step by step.
The Root of the Problem
Your backend is expecting a multipart/form-data request with nested parameters (like course[attachment_attributes][file]), but your previous attempts either:
- Only sent the file without the rest of the course fields, or
- Tried to stuff a FormData object into a JSON body (which doesn't work—JSON can't serialize binary file data, so the backend just sees
[object FormData]instead of the actual file, hence theattachment.file blankerror).
The Fix: Build a FormData with Nested Parameter Names
Instead of mixing JSON and FormData, we'll construct a single FormData object that matches the exact structure your backend is looking for. We use bracket notation (course[title], course[attachment_attributes][file]) to represent the nested fields.
Here's the updated onClickHandler code:
onClickHandler = () => { const formData = new FormData(); // Append all course fields using nested parameter names formData.append('course[title]', 'Hic et velit sed.'); formData.append('course[subtitle]', 'Omnis quibusdam illum itaque.'); formData.append('course[description]', 'Et ullam ipsum. Illum dolor odit. Id veritatis ducimus.'); formData.append('course[end_date]', '2020-02-01'); // Append the file to the nested attachment_attributes field if (this.state.selectedFile) { formData.append('course[attachment_attributes][file]', this.state.selectedFile); } return api .post("http://localhost:3000/api/v1/files", formData) .then(res => { console.log("Upload successful!", res.statusText) }) .catch(err => console.log('Upload error ==> ', err.response?.data || err)) }
Why This Works
- When you use
formData.append('course[attachment_attributes][file]', file), it tells the backend to map the file to the exact nested location it expects (course.attachment_attributes.file). - Most HTTP clients (like Axios, which I assume your
apihelper uses) automatically set the correctContent-Type: multipart/form-dataheader when you pass a FormData object, so you don't need to manually set it.
Important Notes
- Check Your API Helper: Make sure your
apiinstance isn't forcing aContent-Type: application/jsonheader globally. If it is, you'll need to override it for this request, or remove the global setting for uploads. For Axios, you can do this by addingheaders: { 'Content-Type': 'multipart/form-data' }to the post call (though usually Axios handles this automatically). - Single vs Multiple Files: Your input has
multipleset, but your state only storesselectedFile(single file). If you need to upload multiple files, you'll need to loop throughevent.target.filesand append each one with the same parameter name (some backends expect an array likecourse[attachment_attributes][file][]for multiple files).
Full Updated Component Code
Here's the complete component with the fix applied:
import React, { Component } from 'react'; import api from '../../../../helpers/API'; class Uploader extends Component { constructor(props) { super(props); this.state = { selectedFile: null } } onChangeHandler = event => { // If using multiple files, you'd store an array here instead this.setState({ selectedFile: event.target.files[0], loaded: 0 }) } onClickHandler = () => { const formData = new FormData(); // Append course fields formData.append('course[title]', 'Hic et velit sed.'); formData.append('course[subtitle]', 'Omnis quibusdam illum itaque.'); formData.append('course[description]', 'Et ullam ipsum. Illum dolor odit. Id veritatis ducimus.'); formData.append('course[end_date]', '2020-02-01'); // Append file if selected if (this.state.selectedFile) { formData.append('course[attachment_attributes][file]', this.state.selectedFile); } return api .post("http://localhost:3000/api/v1/files", formData) .then(res => { console.log("Upload successful!", res.statusText) }) .catch(err => console.log('Upload error ==> ', err.response?.data || err)) } render() { return ( <> <div> <label>Upload Your File </label> <input type="file" multiple onChange={this.onChangeHandler}/> </div> <button type="button" className="btn btn-success btn-block" onClick={this.onClickHandler}>Upload</button> </> ) } } export default Uploader
Give this a try—your backend should now correctly receive the course data and the file, and the 422 error should disappear!
内容的提问来源于stack exchange,提问作者Joe

