Search code examples
javascriptnode.jsreactjsexpressmulter

React, image is not uploading, getting filename is not defined error


Postman working fine with my backend code. I used form-data there and put a random file. It uploaded successfully to image folder but when it come to React, It is not uploading, and it's showing error on backend and saying filename is not defined. I don't understand the error.

React:

function Regstudent() {
  const [errorMessage, setErrorMessage] = useState("");
  let navigate = useNavigate();

  async function handleRegister(e) {
    e.preventDefault();

    const form = e.target;
    const user = {
      email: form[0].value,
      name: form[1].value,
      gender: form[2].value,
      password: form[3].value,
      batch: form[4].value,
      image: form[5].files,
    };

    try {
      const res = await fetch("http://localhost:8000/api/studentregister", {
        method: "POST",
        headers: {
          "Content-type": "application/json",
        },
        body: JSON.stringify(user),
      });
      const data = await res.json();
      setErrorMessage(data.message);
    } catch (err) {
      setErrorMessage(err);
    }
  }
  return (
    <form encType="multipart/form-data" onSubmit={(e) => handleRegister(e)}>
      <input
        placeholder="Mail Address"
        className="umail"
        type="email"
        name="email"
        id="email"
        required
      />
      <br />
      <input placeholder="Full Name" className="fname" type="text" name="name" id="name" required />
      <br />
      <input
        placeholder="Gender"
        className="gender"
        type="text"
        name="gender"
        id="gender"
        required
      />
      <br />
      <input placeholder="Password" className="password" name="password" type="password" required />
      <br />
      <select name="batch" type="select" required>
        <option value="FSD">FSD</option>
        <option value="CEH">CEH</option>
        <option value="AI">AI</option>
      </select>
      <br />
      <input type="file" accept=".png, .jpg, .jpeg" name="image" />
      <br />
      <input type="submit" className="regbutton" value="Register" />
    </form>
  );
}

Express with Multer:

//multer & uuid

const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, "images");
  },
  filename: function (req, file, cb) {
    cb(null, uuidv4() + "-" + Date.now() + path.extname(file.originalname));
  },
});

const fileFilter = (req, file, cb) => {
  const allowedFileTypes = ["image/jpeg", "image/jpg", "image/png"];
  if (allowedFileTypes.includes(file.mimetype)) {
    cb(null, true);
  } else {
    cb(null, false);
  }
};

let upload = multer({ storage, fileFilter });

//POST
app.post("/api/studentregister", upload.single("image"), async (req, res) => {
  const user = req.body;

  //check user name or pswd is taken before
  const takenEmail = await Users.findOne({ email: user.email });
  const regex = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/i;

  if (takenEmail) {
    res.json({ message: "Email has already been taken" });
  } else if (!regex.test(user.email)) {
    res.json({ message: "Email is invalid" });
  } else {
    user.password = await bcrypt.hash(req.body.password, 10);
    const dbUser = new Users({
      email: user.email.toLowerCase(),
      name: user.name,
      gender: user.gender,
      password: user.password,
      batch: user.batch,
      image: req.file.filename,
      role: "user",
    });
    dbUser.save();
    res.json({ message: "Success" });
  }
});

Solution

  • An easy way to send files with fetch is to use the FormData(), which allows to create and send multipart/form-data. Like so:

    async function handleRegister(e) {
      e.preventDefault();
      const formData = new FormData(e.target);
      try {
        const res = await fetch("http://localhost:8000/api/studentregister", {
          method: "POST",
          body: formData,
        });
        const data = await res.json();
        setErrorMessage(data.message);
      } catch (err) {
        setErrorMessage(err);
      }
    }
    

    Notice I'm giving the HTML form to FormData() with e.target. This will populate the inputs inside formData and avoid you from having to call formData.append(name, value) multiple times. But for this to work each input should have a name property. And also, notice there is no Content-Type set for fetch.