React提交商品数据无法触发后端接口,请求无响应问题排查
问题描述
开发电商项目的商品创建页面时,发送空POST请求到后端能收到响应,但填写商品信息提交后请求卡住,无任何响应。
前端代码(Create_Product.jsx)
import React, { useEffect, useState } from "react"; import AdminMenu from "../../components/AdminMenu"; import Layout from "../../components/Layout/Layout"; import axios from "axios"; import { Select } from "antd"; import { toast } from "react-toastify"; const { Option } = Select; export default function CreateProduct() { const [category, setCategory] = useState(""); const [categories, setCategories] = useState([]); const [product, setProduct] = useState({ name: "", description: "", price: "", photo: "", isShipping: "", category: "", quantity: "", }); useEffect(() => { getAllCategories(); }, []); async function getAllCategories() { try { const { data } = await axios.get( process.env.REACT_APP_API + "api/category/get-category" ); if (data.success) setCategories(data.data); } catch (e) { toast.error("Something went wrong while fetching Categories"); console.log(e); } } async function handleOnClick(e) { e.preventDefault(); try { const response = await axios.post( process.env.REACT_APP_API + "api/product/create-product", product ); if (response.data.success) { toast.success(response.data.message); } } catch (error) { console.log(error); } } // console.log(product); function handleOnChange(e) { const { name, value } = e.target; if (name === "photo") { const file = e.target.files[0]; if (file) { setProduct((prevValue) => { return { ...prevValue, photo: file }; }); } } else { setProduct((prevValue) => { return { ...prevValue, [name]: value }; }); } } return ( <Layout> <div className="container-fluid"> <div className="row"> <div className="col-md-3"> <AdminMenu /> </div> <div className="col-md-9"> <h2 className="mb-2">Create Product</h2> <Select showSearch showArrow placeholder="Select Category" className="w-50 " onChange={(value) => setProduct((prevValue) => ({ ...prevValue, category: value, })) } > {categories?.map((item, i) => { return ( <Option key={i} value={item._id}> {item.name} </Option> ); })} </Select> <div className="mb-2"> {product.photo && ( <div className="text-left"> <img src={URL.createObjectURL(product.photo)} className="img img-responsive" height={"200px"} alt="product-photo" /> </div> )} </div> <div className="mb-2"> <label className="btn btn-outline-secondary mt-2"> {" "} {product.photo ? product.photo.name : "Upload Photo"} <input type="file" name="photo" accept="image/*" hidden onChange={handleOnChange} /> </label> </div> <div className="mb-2"> <form> <div className="form-group"> <label htmlFor="exampleInputEmail1">Product Name</label> <input type="text" className="form-control mb-2 w-50" placeholder="Enter product name" value={product.name} name="name" onChange={handleOnChange} /> </div> <div className="form-group"> <label htmlFor="description">Description</label> <textarea type="text" name="description" className="form-control mb-2 w-50" rows={3} placeholder="Description" value={product.description} onChange={handleOnChange} /> </div> <div className="form-group"> <label htmlFor="exampleInputEmail1">Product price</label> <input type="text" className="form-control mb-2 w-50" placeholder="Price" value={product.price} name="price" onChange={handleOnChange} /> </div> <div className="form-group"> <label htmlFor="exampleInputEmail1">Product quantity</label> <input type="text" className="form-control mb-2 w-50" placeholder="Quantity" value={product.quantity} name="quantity" onChange={handleOnChange} /> </div> <div className="form-group"> <label htmlFor="Shipping">Shipping ?</label> <Select placeholder="Is product shipping available" className="w-50 form-control mb-4" onChange={(value) => setProduct((prevValue) => ({ ...prevValue, isShipping: value, })) } name="isShipping" > <Option value={true}>TRUE</Option> <Option value={false}>FALSE</Option> </Select> </div> <div className="form-group"> <button type="submit" className="btn btn-success" onClick={handleOnClick} > Create product </button> </div> </form> </div> </div> </div> </div> </Layout> ); }
后端代码
路由配置
router.post('/create-product',requireSignIn,isAdmin,formidable(),createProductController);
核心控制器代码
import slugify from "slugify"; import Product from "../../model/productModel.js"; import fs from "fs"; export default async function createProductController(req, res) { try { console.log("I am triggered"); const { name, description, price, category, isShipping , quantity } = req.fields; console.log( name, description, price, category, isShipping , quantity); const { photo } = req.files; if (!name || !description || !price || !category || !photo || !quantity) { return res .status(200) .json({ success: false, message: "Empty fields found" }); } if (photo.size > 100000) { return res .status(200) .json({ success: false, message: "Photo size limit crossed" }); } const product = new Product({ ...req.fields, slug: slugify(name), }) if (photo) { product.photo.data = fs.readFileSync(photo.path); product.photo.contentType = photo.type; } await product.save(); return res .status(200) .json({ success: true, message: "Product created successfully" }); } catch (e) { console.log(e); return res .status(500) .json({ success: false, message: "Error in product creation", e }); } }
解决方案
问题根源是前端提交数据格式与后端期望不匹配:后端用formidable中间件处理multipart/form-data类型的文件上传请求,但前端直接发送了JSON对象,导致后端无法解析请求,进而卡住。
1. 修改前端提交逻辑,使用FormData发送数据
修改handleOnClick函数,将商品数据包装为FormData对象再提交:
async function handleOnClick(e) { e.preventDefault(); try { const formData = new FormData(); // 遍历product对象,添加所有非空字段和文件 Object.entries(product).forEach(([key, value]) => { if (value) { formData.append(key, value); } }); const response = await axios.post( process.env.REACT_APP_API + "api/product/create-product", formData ); if (response.data.success) { toast.success(response.data.message); // 提交成功后重置表单 setProduct({ name: "", description: "", price: "", photo: "", isShipping: "", category: "", quantity: "" }); } } catch (error) { console.error(error); toast.error(error.response?.data?.message || "商品创建失败"); } }
2. 可选:优化后端字段类型处理
前端输入的price、quantity是字符串,isShipping是布尔值字符串,后端可做类型转换避免数据库存储异常:
export default async function createProductController(req, res) { try { console.log("I am triggered"); const { name, description, price, category, isShipping , quantity } = req.fields; console.log( name, description, price, category, isShipping , quantity); const { photo } = req.files; // 类型转换 const parsedPrice = parseFloat(price); const parsedQuantity = parseInt(quantity, 10); const parsedIsShipping = isShipping === 'true' ? true : false; if (!name || !description || !parsedPrice || !category || !photo || !parsedQuantity) { return res .status(200) .json({ success: false, message: "Empty fields found" }); } if (photo.size > 100000) { return res .status(200) .json({ success: false, message: "Photo size limit crossed" }); } const product = new Product({ name, description, price: parsedPrice, category, isShipping: parsedIsShipping, quantity: parsedQuantity, slug: slugify(name), }) if (photo) { product.photo.data = fs.readFileSync(photo.path); product.photo.contentType = photo.type; } await product.save(); return res .status(200) .json({ success: true, message: "Product created successfully" }); } catch (e) { console.log(e); return res .status(500) .json({ success: false, message: "Error in product creation", e }); } }
内容的提问来源于stack exchange,提问作者Binit Kumar
相关产品推荐
相关产品推荐

