React+Express中FormData传数据后端获值为undefined问题排查
React+Express项目FormData传输后后端字段undefined的问题分析与解决
问题描述
我用React前端+Express后端开发项目,实现数据编辑功能时因需传输文件采用FormData。前端确认FormData内容完整,但后端接收时req.body.ship_agent_name等字段显示为undefined,相关代码如下:
前端modal.jsx代码
const EditModal = ({type, isOpen, setIsOpen, rowId, onEditSuccess }) => { const [shipAgentName, setShipAgentName] = useState(""); const [shipAgentCompany, setShipAgentCompany] = useState(""); const [arrivalSchedule, setArrivalSchedule] = useState(""); const [arrivalDestination, setArrivalDestination] = useState(""); const [departureSchedule, setDepartureSchedule] = useState(""); const [departureDestination, setDepartureDestination] = useState(""); const [file, setFile] = useState(null); const [filename, setFilename] = useState(""); useEffect(() => { const fetchData = async () => { try { const response = await fetch( "http://localhost:3001/api/ship/arrival/" + rowId, { method: "GET", headers: { Authorization: `Bearer ${token}`, }, } ); if (response.ok) { const data = await response.json(); setShipAgentName(data.shipAgentName); setShipAgentCompany(data.shipAgentCompany); if(type === "arv"){ setArrivalSchedule(data.arrivalSchedule); setArrivalDestination(data.arrivalDestination); }else{ setDepartureSchedule(data.departureSchedule); setDepartureDestination(data.departureDestination); } setFilename(data.document); } else { console.log("Failed to fetch data"); } } catch (err) { console.log(err.message); } }; fetchData(); }, [rowId, token]); const editRow = async (e) => { e.preventDefault(); const formDataa = new FormData(); formDataa.append("document", file); formDataa.append("ship_agent_name", shipAgentName); formDataa.append("ship_agent_company", shipAgentCompany); if(type === "arv"){ formDataa.append("arrival_schedule", arrivalSchedule); formDataa.append("arrival_destination", arrivalDestination); }else{ formDataa.append("departure_schedule", departureSchedule); formDataa.append("departure_destination", departureDestination); } try { const response = await fetch( "http://localhost:3001/api/ship/arrival/" + rowId, { method: "PUT", body: formDataa, headers: { Authorization: `Bearer ${token}`, }, } ); if (response.ok) { console.log("Data successfully updated"); onEditSuccess(); } else { console.log("Failed to update data"); } } catch (err) { console.log(err.message); } finally { setIsOpen(false); } }; return ( <div id="popup-modal" tabIndex="-1" className={ "overflow-y-auto overflow-x-hidden z-50 justify-center items-center flex fixed bg-[rgba(0,0,0,0.2)] transition transform h-screen w-full" } onClick={(e) => handleClickOutside(e)} > // ... <form className="flex justify-center items-center" onSubmit={(e) => {editRow(e, rowId)}} encType="multipart/form-data"> <div className="grid gap-[20px]"> <div> <div className="pl-[17px] font-bold">Agent Name</div> <div className="pt-[11px]"> <input type="text" value={ship_agent_name} onChange={(e) => setShipAgentName(e.target.value)} placeholder="Agents Name" className="pl-[17px] bg-[#83B3CA] placeholder-[#5C5C68] w-[594px] h-[45px] rounded-[10px]" /> // ... </form> };
后端index.js代码
const app = express(); const corsOptions = { origin: 'http://localhost:3000', credentials: true, methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', }; app.use(cookieParser()); app.use(cors(corsOptions)); app.use(express.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use('/public', express.static(path.join(__dirname, "..", 'public'))); //.... app.put("/api/ship/:type/:id", verifyToken, (req, res) => { const id = req.params.id; const type = req.params.type; console.log(req.body.ship_agent_name); if(req.body.ship_agent_name === undefined){ console.log("no data"); return res.status(500).send({ message: "Error updating data" }); } let sqlQuery = "SELECT * FROM departures WHERE id = ?"; if (type === "arrival") { sqlQuery = "SELECT * FROM arrivals WHERE id = ?"; } db.query(sqlQuery, [id], (err, result) => { if (err) { return res.status(500).send({ message: "Error fetching data" }); } let arrivalSchedule, arrivalDestination, departureSchedule, departureDestination; if (result.length > 0) { const shipAgentName = req.body.ship_agent_name || result[0].ship_agent_name; const shipAgentCompany = req.body.ship_agent_company || result[0].ship_agent_company; const document = req.file ? req.file.filename : result[0].document; if (type === "arrival") { arrivalSchedule = req.body.arrival_schedule || result[0].arrival_schedule; arrivalDestination = req.body.arrival_destination || result[0].arrival_destination; } else { departureSchedule = req.body.departure_schedule || result[0].departure_schedule; departureDestination = req.body.departure_destination || result[0].departure_destination; } const updateQuery = ` UPDATE ${type === "arrival" ? "arrivals" : "departures"} SET ship_agent_name = ?, ship_agent_company = ?, ${type === "arrival" ? "arrival_schedule" : "departure_schedule"} = ?, ${type === "arrival" ? "arrival_destination" : "departure_destination"} = ?, document = ? WHERE id = ?`; const updateData = [shipAgentName, shipAgentCompany, arrivalSchedule || departureSchedule, arrivalDestination || departureDestination, document, id]; db.query(updateQuery, updateData, (err, result) => { if (err) { res.status(500).send({ message: "Error updating data" }); } else { res.send({ message: "Data successfully updated" }); } }); } }); }); //...
问题原因
1. 后端未配置FormData解析中间件
Express默认的express.json()和bodyParser.urlencoded({ extended: true })仅能处理JSON和urlencoded格式的请求体,完全无法解析multipart/form-data类型的FormData(尤其是包含文件的情况)。这直接导致后端无法读取FormData中的字段,req.body为空或字段值为undefined。
2. 前端输入框状态绑定错误
前端输入框的value属性绑定了未定义的ship_agent_name变量,正确的状态变量应为shipAgentName。虽然你确认FormData内容完整,但这个错误会导致输入框无法正确显示初始值,用户输入时也可能出现异常,建议修复。
解决办法
步骤1:后端添加multer中间件处理FormData
- 安装multer:
npm install multer
- 引入并配置multer(指定文件存储路径和命名规则):
const multer = require('multer'); const path = require('path'); // 配置文件存储 const storage = multer.diskStorage({ destination: (req, file, cb) => { // 文件存储到public目录 cb(null, path.join(__dirname, "..", 'public')); }, filename: (req, file, cb) => { // 用时间戳+原文件名避免重复 cb(null, `${Date.now()}-${file.originalname}`); } }); const upload = multer({ storage: storage });
- 修改PUT接口,加入
upload.single('document')中间件(对应FormData中文件字段名document):
// 注意添加upload.single('document')中间件 app.put("/api/ship/:type/:id", verifyToken, upload.single('document'), (req, res) => { const id = req.params.id; const type = req.params.type; console.log(req.body.ship_agent_name); // 现在能正常获取值 // ... 后续业务逻辑不变 });
步骤2:修复前端输入框绑定错误
将输入框的value={ship_agent_name}改为value={shipAgentName},确保状态与输入框正确绑定:
<input type="text" value={shipAgentName} onChange={(e) => setShipAgentName(e.target.value)} placeholder="Agents Name" className="pl-[17px] bg-[#83B3CA] placeholder-[#5C5C68] w-[594px] h-[45px] rounded-[10px]" />
步骤3:保持前端请求的Content-Type自动设置
使用fetch发送FormData时,浏览器会自动生成正确的Content-Type: multipart/form-data; boundary=xxx请求头,不要手动添加该字段,否则会导致解析错误。你当前的前端请求代码已符合要求,无需修改。
内容的提问来源于stack exchange,提问作者Papazy
相关产品推荐
相关产品推荐

