0

I'm having trouble trying to upload an image from my React component using Express on the backend side and Multer as the middleware to manage the upload process. I've configured Multer on the backend side and also used Axios in my React component to send POST requests to the appropriate endpoints. However, I keep getting error 500 messages and I'm not sure what's going wrong.

server.js:

import express from "express";
import dotenv from "dotenv";
dotenv.config();
import cors from "cors";
import cookieParser from "cookie-parser";
import corsOptions from "./utils/corsOptions.js";
import multer from "multer";

import authRoute from "./routes/authRoute.js";
import userRoute from "./routes/userRoute.js";
import postRoute from "./routes/postRoute.js";

const port = process.env.APP_PORT || 5000;

const app = express();

app.use(cors(corsOptions));

app.use(express.json());
app.use(cookieParser());

const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, "../frontend/public/upload");
  },
  filename: function (req, file, cb) {
    cb(null, Date.now() + file.originalname);
  },
});

const upload = multer({ storage });

app.post("/api/v1/upload", upload.single("file"), function (req, res) {
  const file = req.file;
  res.status(200).json(file.filename);
});

app.use("/api/v1", authRoute);
app.use("/api/v1", userRoute);
app.use("/api/v1", postRoute);

app.listen(port, () => {
  console.log(`Server started on port ${port}`);
});

ModalEditProfile.jsx:

import { useContext, useState } from "react";
import { AuthContext } from "../../../context/AuthContext";
import PropTypes from "prop-types";
import "./modalEditProfile.scss";
import axios from "axios";

const ModalEditProfile = ({ onClose, onUpdateProfile }) => {
  const { currentUser } = useContext(AuthContext);
  const [formData, setFormData] = useState({
    username: currentUser.username,
    email: currentUser.email,
    image: currentUser.image,
  });

  const handleChange = (e) => {
    const { name, value, type } = e.target;

    // If the input is a file input (for image), handle it separately
    if (type === "file") {
      const file = e.target.files[0];
      const reader = new FileReader();

      reader.onloadend = () => {
        setFormData((prevFormData) => ({
          ...prevFormData,
          image: reader.result, // Update the image value with the base64 data
        }));
      };

      if (file) {
        reader.readAsDataURL(file);
      }
    } else {
      setFormData((prevFormData) => ({
        ...prevFormData,
        [name]: value,
      }));
    }
  };

  const uploadImage = async (imageFile) => {
    try {
      const formDataToSend = new FormData();
      formDataToSend.append("file", imageFile);

      const response = await axios.post(
        "http://localhost:5000/api/v1/upload",
        formDataToSend
      );
      return response.data.imageUrl;
    } catch (error) {
      console.log("Error uploading image:", error);
      return null;
    }
  };

  const handleSubmit = async (e) => {
    e.preventDefault();

    let imageUrl = currentUser.image; // Default image URL, in case no new image is uploaded

    if (formData.image) {
      // Jika ada gambar yang diunggah, upload gambar dan dapatkan URL gambar baru
      imageUrl = await uploadImage(formData.image);
    }

    // Update data profil pengguna dengan URL gambar baru
    const updatedData = { ...formData, image: imageUrl };
    onUpdateProfile(updatedData);
    onClose();
  };

  return (
    <div className="modal">
      <div className="modal-content">
        <h2>Edit Profile</h2>
        <form onSubmit={handleSubmit} encType="multipart/form-data">
          <div className="form-group">
            <label htmlFor="username">Username:</label>
            <input
              type="text"
              id="username"
              name="username"
              value={formData.username}
              onChange={handleChange}
              className="input-field"
            />
          </div>
          <div className="form-group">
            <label htmlFor="email">Email:</label>
            <input
              type="email"
              id="email"
              name="email"
              value={formData.email}
              onChange={handleChange}
              className="input-field"
            />
          </div>
          <div className="form-group">
            <label htmlFor="image">Profile Image:</label>
            <input
              type="file"
              id="image"
              name="image"
              accept="image/*"
              onChange={handleChange}
              className="input-field"
            />
            {formData.image && (
              <img
                src={formData.image}
                alt="Profile"
                className="preview-image"
              />
            )}
          </div>
          {/* Add other fields that can be edited here */}
          <div className="form-actions">
            <button type="submit" className="submit-btn">
              Save Changes
            </button>
            <button type="button" onClick={onClose} className="cancel-btn">
              Cancel
            </button>
          </div>
        </form>
      </div>
    </div>
  );
};

ModalEditProfile.propTypes = {
  onClose: PropTypes.func.isRequired,
  onUpdateProfile: PropTypes.func.isRequired,
};

export default ModalEditProfile;

When trying to upload an image using the ModalEditProfile component, I get a 500 (Internal Server Error) error on the browser side. The error message that appears is:

ModalEditProfile.jsx:67 POST http://localhost:5000/api/v1/upload 500 (Internal Server Error)
Error uploading image: AxiosError {message: 'Request failed with status code 500', name: 'AxiosError', code: 'ERR_BAD_RESPONSE', ...}

On the backend side, I see the following error message:

TypeError: Cannot read properties of undefined (reading 'filename')
    at file:///C:/Users/ayub/Documents/latihan/my-blog/backend/src/server.js:35:29
    at Layer.handle [as handle_request] (C:\Users\ayub\Documents\latihan\my-blog\backend\node_modules\express\lib\router\layer.js:95:5)
    at next (C:\Users\ayub\Documents\latihan\my-blog\backend\node_modules\express\lib\router\route.js:144:13)
    at done (C:\Users\ayub\Documents\latihan\my-blog\backend\node_modules\multer\lib\make-middleware.js:45:7)
    at indicateDone (C:\Users\ayub\Documents\latihan\my-blog\backend\node_modules\multer\lib\make-middleware.js:49:68)
    at Multipart.<anonymous> (C:\Users\ayub\Documents\latihan\my-blog\backend\node_modules\multer\lib\make-middleware.js:166:7)
    at Multipart.emit (node:events:513:28)
    at emitCloseNT (node:internal/streams/destroy:132:10)
    at process.processTicksAndRejections (node:internal/process/task_queues:81:21)

I'm sure the Multer configuration on the backend side is correct, as I can upload images from other components (such as the "write" component) without issue.

However, when I try to upload an image from the ModalEditProfile component, I get a 500 error message on the frontend side, and a "TypeError: Cannot read properties of undefined (reading 'filename')" error message on the backend side.

I tried to find a solution but haven't found the right answer yet. Can anyone help me check my code and tell what might be wrong?

Thank you for the help!

Steps Already Done: I have checked the multer configuration on the backend side and confirmed that the image storage destination has been specified correctly. I've also verified that the write component is successfully uploading images properly using the same configuration.

Question: Why am I getting error 500 (Internal Server Error) when trying to upload an image using the ModalEditProfile component? What causes the "TypeError: Cannot read properties of undefined (reading 'filename')" error on the backend side? How do I fix this problem?

1 Answers1

0

In your handleChange function, when handling the file input, you are updating the formData state with the base64 data URL of the selected image. Then when you call the uploadImage function, you are passing formData.image, which is the base64 string, not the actual file object that axios and multer expect. Though the file object is not stored in formData, I think you should be able to access it directly from the file input using e.target.files[0].

const handleSubmit = async (e) => {
   ...
    if (formData.image) {
      imageUrl = await uploadImage(e.target.files[0]);
    }
   ...
}
Nazrul Chowdhury
  • 1,483
  • 1
  • 5
  • 11