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

MERN+TypeScript应用中React表单图片上传失败求助

React + TypeScript 图片上传失败问题解决

我用TypeScript开发React前端,后端基于Express(TypeScript)+Mongoose搭建,通过ThunderClient测试图片上传API完全正常,但在React表单中上传图片完全失效,尝试多种方法仍未解决,相关代码如下:

前端代码

import axios from "axios";
import { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";

const ProductCreate = () => {
const [title, setTitle] = useState<string>("");
const [price, setPrice] = useState<string>("");
const [ImageFile, setImageFile] = useState<File | null>(null);
const [category, setCategory] = useState<string>("");
const [description, setDescription] = useState<string>("");
const [isLoading, setLoading] = useState<boolean>(false);
const [isError, setError] = useState<boolean>(false);
const [data, setData] = useState<[]>([]);
const navigate = useNavigate();

interface handleCategory {
  _id: string;
  Name: string;
}

useEffect(() => {
  const fetchCategory = async () => {
    try {
      const res = await axios.get("http://localhost:5000/category");
      await setData(res.data);
    } catch (error) {
      console.log(error);
    }
  };
  fetchCategory();
}, []);

//  Submitting the form
const handleSubmit = async (e: any) => {
  e.preventDefault();
  const body = {
    title,
    price,
    description,
    category,
    ImageFile,
  };
  console.log(body);
  try {
    const res = await axios.post(
      "http://localhost:5000/product/create",
      body,
    );
    await res.data;
    navigate("/products");
    console.log(res.data);
  } catch (err: any) {
    setError(err);
    console.log(err);
  }
};

return (
  <div className='productCreate'>
    {isError && <p> Something is wrong</p>}
    <div>
      <form
        className='prForm'
        onSubmit={handleSubmit}
        encType='multipart/form-data'
      >
        <h3> Create Product</h3>
        <div className='Form_Control'>
          <label>Product Title</label>
          <input
            onChange={(e) => setTitle(e.target.value)}
            type='text'
            placeholder='Input title'
          />
        </div>
        <div className='Form_Control'>
          <label>Product Image</label>
          <input
            type='file'
            name='image'
            onChange={(e) =>
              setImageFile(e.target.files ? e.target.files[0] : null)
            }
          />
        </div>
        <div className='Form_Control'>
          <label>Price</label>
          <input
            onChange={(e) => setPrice(e.target.value)}
            type='text'
            placeholder='Input  price'
          />
        </div>
        <div className='Form_Control'>
          <label>Product Category</label>
          <select onChange={(e) => setCategory(e.target.value)} id=''>
            {data.map((data: handleCategory) => (
              <option key={data._id} value={data._id}>
                {data.Name}
              </option>
            ))}
          </select>
        </div>
        <div className='Form_Control'>
          <label>Description</label>
          <textarea onChange={(e) => setDescription(e.target.value)} />
        </div>
        <button type='submit'> Create</button>
      </form>
    </div>
  </div>
);
};

export default ProductCreate;

后端路由代码

import { Router } from "express";
import {
  getAllProduct,
  getProductByID,
  createProduct,
  countProduct,
  updateProduct,
  deleteProduct,
} from "../controller/productController";
import multer from "multer";
import path from "path";

const router = Router();

const storage = multer.diskStorage({
  destination: (req, file, cb) => cb(null, "./upload"),
  filename: (req, file, cb) => {
    console.log(file);
    cb(null, Date.now() + path.extname(file.originalname));
  },
});

const upload = multer({ storage });

router.get("/", getAllProduct);
router.get("/:id", getProductByID);
router.get("/count", countProduct);
router.post("/create", upload.single("ImageFile"), createProduct);
router.put("/:id", upload.single("ImageFlie"), updateProduct);
router.delete("/:id", deleteProduct);

export default router;

后端控制器代码

import { Request, Response } from "express";
import product from "../model/productModel";

// Get all product
const getAllProduct = async (req: Request, res: Response) => {
  try {
    const findproduct = await product.find().populate("category").exec();
    res.status(200).json(findproduct);
  } catch (err: any) {
    res.status(500).json(err.message);
  }
};

// Count all product
const countProduct = async (req: Request, res: Response) => {
  const count = await product.find().count().exec();
  console.log(count);

  res.status(200).json({ count });
};

const getProductByID = async (req: Request, res: Response) => {
  try {
    const id = req.params.id;
    const getSingleProduct = await product.findById(id);
    res.status(200).json(getSingleProduct);
  } catch (err: any) {
    res.status(500).json(err.message);
  }
};
const createProduct = async (req: Request, res: Response) => {
  try {
    const newProducs = new product({
      title: req.body.title,
      description: req.body.description,
      price: req.body.price,
      category: req.body.category,
      image: req.file?.filename,
    });
    const savedProduct = await newProducs.save();
    res.status(200).json(savedProduct);
  } catch (err: any) {
    res.status(500).json(err.message);
  }
};
const updateProduct = async (req: Request, res: Response) => {
  try {
    const id = req.params.id;
    const result = await product.findByIdAndUpdate(
      id,
      { $set: req.body },
      { new: true },
    );
    res.status(200).json(result);
  } catch (err: any) {
    res.status(500).json(err.message);
  }
};
const deleteProduct = async (req: Request, res: Response) => {
  try {
    const id = req.params.id;
    const deletedProduct = await product.findByIdAndDelete(id);
    res.status(200).json("Deleted Successfully");
  } catch (err: any) {
    res.status(500).json(err.message);
  }
};

export {
  getAllProduct,
  getProductByID,
  countProduct,
  createProduct,
  updateProduct,
  deleteProduct,
};

问题修复方案

1. 前端核心修改

上传文件必须使用FormData对象封装数据,同时保证文件字段名与后端一致:

// 修改后的handleSubmit函数
const handleSubmit = async (e: any) => {
  e.preventDefault();
  // 创建FormData对象
  const formData = new FormData();
  // 追加表单字段
  formData.append("title", title);
  formData.append("price", price);
  formData.append("description", description);
  formData.append("category", category);
  // 追加文件,字段名必须和后端multer配置的"ImageFile"一致
  if (ImageFile) {
    formData.append("ImageFile", ImageFile);
  }

  try {
    const res = await axios.post(
      "http://localhost:5000/product/create",
      formData
      // axios会自动识别FormData并设置正确的Content-Type,无需手动设置
    );
    navigate("/products");
    console.log(res.data);
  } catch (err: any) {
    setError(true);
    console.log(err);
  }
};

// 同时修改文件输入框的name属性,和后端保持一致
<input
  type='file'
  name='ImageFile' // 从"image"改为"ImageFile"
  onChange={(e) =>
    setImageFile(e.target.files ? e.target.files[0] : null)
  }
/>

2. 后端路由笔误修复

update路由中upload.single("ImageFlie")存在拼写错误,改为ImageFile:

router.put("/:id", upload.single("ImageFile"), updateProduct);

3. 额外检查

确保后端./upload目录存在,multer不会自动创建目录,若不存在需手动创建,否则会报错。


内容的提问来源于stack exchange,提问作者clemon ezeh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 18:05:18