1

I want to upload multiple images from my reactjs frontend using DropZone. I have configured the backend to upload multiple images but everytime i try to upload images no images get uploaded and i recieve an empty array for the images.

in my upload.js file the getResult() function always returns this

return res.send(`You must select at least 1 image.`);

Find this image to view the data in my payload

How can i fix this?

Here is my code:

NodeJs Backend upload.js:

const multer = require("multer");
const sharp = require("sharp");

const multerStorage = multer.memoryStorage();

const multerFilter = (req, file, cb) => {
    if (file.mimetype.startsWith("image")) {
        cb(null, true);
    } else {
        cb("Please upload only images.", false);
    }
};

const upload = multer({
    storage: multerStorage,
    fileFilter: multerFilter
});

const uploadFiles = upload.array("images", 10);

const uploadImages = (req, res, next) => {
    uploadFiles(req, res, err => {
        if (err instanceof multer.MulterError) {
            if (err.code === "LIMIT_UNEXPECTED_FILE") {
                return res.send("Too many files to upload.");
            }
        } else if (err) {
            return res.send(err);
        }

        next();
    });
};

const resizeImages = async (req, res, next) => {
    if (!req.files) return next();

    req.body.images = [];
    await Promise.all(
        req.files.map(async file => {
            const filename = file.originalname.replace(/\..+$/, "");
            const newFilename = `bezkoder-${filename}-${Date.now()}.jpeg`;

            await sharp(file.buffer)
                .resize(640, 320)
                .toFormat("jpeg")
                .jpeg({ quality: 90 })
                .toFile(`images/${newFilename}`);

            req.body.images.push(newFilename);
        })
    );

    next();
};

const getResult = async (req, res, next) => {
    console.log(req.body.images)
    if (req.body.images.length <= 0) {
        return res.send(`You must select at least 1 image.`);
    }

    const images =  JSON.parse(req.body.images)
        .map(image => "" + image + "")
        .join("");

    // return res.send(`Images were uploaded:${images}`);
    next();
};

module.exports = {
    uploadImages: uploadImages,
    resizeImages: resizeImages,
    getResult: getResult
};

then in my controller projects.js:

router.post('/create-project', [upload.uploadImages, upload.resizeImages, upload.getResult], (req, res) => {
          //my code here
})

ReactJs Frontend createProjects:

const [data, setData] = useState({
    projectName: '',
    projectDesc: ''
  });
  const [startDate, setStartDate] = useState(new Date());
  const [endDate, setEndDate] = useState(new Date());
  const [selectedFiles, setSelectedFiles] = useState([]);

  const handleChange = (e) => {
    setData({ ...data, [e.target.name]: e.target.value });
  }

  const startDateChange = date => {
    setStartDate(date);
  };

  const endDateChange = date => {
    setEndDate(date);
  };


  function handleAcceptedFiles(files) {
    files.map(file =>
      Object.assign(file, {
        preview: URL.createObjectURL(file),
        formattedSize: formatBytes(file.size)
      })
    );
    setSelectedFiles(files);
  }

  function formatBytes(bytes, decimals = 2) {
    if (bytes === 0) return "0 Bytes";
    const k = 1024;
    const dm = decimals < 0 ? 0 : decimals;
    const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];

    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
  }

  const createProject = async (e) => {
    e.preventDefault();
    const formData = new FormData()
    formData.append("projectName", data.projectName);
    formData.append("projectDesc", data.projectDesc);
    formData.append("startDate", JSON.stringify(startDate));
    formData.append("projectName", JSON.stringify(endDate));
    formData.append("images", JSON.stringify(selectedFiles));
    addProject(formData);
  }

return (


                       <Form>
                        <Dropzone
                          onDrop={acceptedFiles => {
                            handleAcceptedFiles(acceptedFiles);
                          }}
                          name="images"
                        >
                          {({ getRootProps, getInputProps }) => (
                            <div className="dropzone">
                              <div
                                className="dz-message needsclick"
                                {...getRootProps()}
                              >
                                <input {...getInputProps()} name="images" type="file" multiple />
                                <div className="dz-message needsclick">
                                  <div className="mb-3">
                                    <i className="display-4 text-muted bx bxs-cloud-upload" />
                                  </div>
                                  <h4>Drop files here or click to upload.</h4>
                                </div>
                              </div>
                            </div>
                          )}
                        </Dropzone>
                       </Form>
)
motionless570
  • 925
  • 1
  • 10
  • 26

2 Answers2

0

Try appending each file, instad of stringifying them:

//... 

formData.append("projectName", JSON.stringify(endDate));

for (const file of selectedFiles) {
    
    formData.append("images", file);

}

addProject(formData);

Also, it seems you're trying to add custom properties to the file, this won't work, try something like this: File object with custom properties

traynor
  • 5,490
  • 3
  • 13
  • 23
  • Thank you for your answer, but how will the backend handle the images and upload them to my images folder? i am not trying to add custom properties, these are the properties that get added when i upload the files in the dropzone – motionless570 Feb 23 '23 at 13:52
  • Thank you, your solution worked, but for some reason selectedFiles always contains 1 image file even if i put more than 1 image. i keep adding 2 or 3 images but when i log the formdata its only 1 image. why is that? – motionless570 Feb 23 '23 at 14:26
  • can you log `selectedFiles`/`files`. you probably need to modify `handleChange` to include `e.target.files`: https://stackoverflow.com/questions/68213700/react-js-upload-multiple-files – traynor Feb 24 '23 at 06:52
0

I finally found the solution that worked correctly for my usecase i had to use the forEach() function to append each file to the formData()

const formData = new FormData()
    formData.append("projectName", data.projectName);
    formData.append("projectDesc", data.projectDesc);
    formData.append("comments[]", comment);
    formData.append("startDate", startDate.toISOString());
    formData.append("dueDate", endDate.toISOString());
    selectedFiles.forEach((images, index) => {
      formData.append(`images`, images);
    });
    addProject(formData);
motionless570
  • 925
  • 1
  • 10
  • 26
  • ..and how this is different from the `for..of` loop.? See: [Should one use for-of or forEach when iterating through an array?](https://stackoverflow.com/questions/50844095/should-one-use-for-of-or-foreach-when-iterating-through-an-array) – traynor Feb 26 '23 at 19:43