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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 07:18:09