Search code examples
reactjsexpressfile-uploadcloudinaryrtk-query

Unable to upload file through react frontend


I am trying to upload the image from React Frontend to Cloudinary. I wrote a backend API called upload image. when I try through Postman it works fine the files are uploaded to Cloudinary Server but when I try through my React it shows the file not received. It really helps if anyone go through this code I share tell me the mistake I am making and why my backend is not getting the file.

dataUri.js

import DataUriParser from "datauri/parser.js";
import path from "path";

const getDataURI = (file) => {
    const parser = new DataUriParser();
    const extName = path.extname(file.originalname).toString();
    return parser.format(extName, file.buffer);
};

export default getDataURI;

multerMiddleware.js

which I use in the route

import multer from "multer";

const storage = multer.memoryStorage();

const singleUpload = multer({ storage }).single("file");

export default singleUpload;

My Image Controller

export const uploadImagesUrl = expressAsyncHandler(async (req, res) => {
    try {

        const field = req.params.field;
        try {
            const file = req.file;

            if (!file) {
                return res.status(400).json({ error: "No file received" });
            }

            const fileURI = getDataURI(file);

            const imgUpload = await cloudinaryConfig();

            const { cloud_name } = imgUpload.config();

            const result = await imgUpload.uploader.upload(fileURI.content, {
                folder: "JewelryMine",
            });

            const image = await Image.findByIdAndUpdate(process.env.IMAGE_CONFIGURATION_ID);

            const image_url = `https://res.cloudinary.com/${cloud_name}/image/upload/${result.public_id}`;

            const updateObj = {
                $set: {
                    [`${field}.public_id`]: result.public_id,
                    [`${field}.image_url`]: image_url,
                },
            };

            if (image) {
                await image.updateOne(updateObj, options)
                return res.json({ message: "File Uploaded" });
            } else {
                return res.status(404).json({ message: "Field not found" });
            }
        } catch (error) {
            console.error(error);
            res.status(500).json({ error: "Upload failed" });
        }

        //
    } catch (error) {
        res.status(500);
        throw new Error("Server Error. Please try after sometime.");
    }
});

And My React File

form

    <div className='mt-2'>
    <form onSubmit={submitHandler}>
        <div className='flex flex-col'>
            <input type='file' accept='image/*' onChange={handleImage} required />
        </div>
        <div className='flex flex-col'>
          <label >Preview</label>
          <img src={imgPreview} required />
        </div>
        <div className='mt-4'>
            <button type='submit'>Update</button>
        </div>
    </form>
  </div>

handleChange and handle Submit Part

const [image, setImage] = useState(null);
    const [imgPreview, setImgPreview] = useState("");
const [updateImageUrl] = useUpdateImageUrlMutation();
    
    const handleImage = async (event) => {
        setImage(event.target.files[0]);
        const url = await URL.createObjectURL(event.target.files[0] || image);
        setImgPreview(url);
    };

    const submitHandler = async (event) => {
        event.preventDefault();

        const formData = new FormData();
        formData.append("file", image);

        try {
            const res = await updateImageUrl({ field, formData }).unwrap();

            if (res) {
                toast.success(res.message || res.data.message);
            }
        } catch (err) {
            toast.error(err?.data?.message || err?.error || err?.data?.error);
        }
    };

Solution

  • Updating with an answer based on the chat discussion:

    There were two issues:

    1. When sending the file from the client side the Content-Type didn't match the data being sent (it likely defaulted to application/json) while the content was actually multipart/form-data. This caused the server not to be able to parse the request correctly.
    2. The value of the body in the RTK query was containing both the formData and field based on the call to updateImageUrl inside handleSubmit.

    The solution for 1) was to set formData: true in the RTK query and that will allow the Content-Type header to be set automatically to multipart/form-data as part of the request to match the data being sent.

    For 2) the value of the body was set to data.formData.

    The resulting RTK query with the above changes applied:

    updateImageUrl: builder.mutation({
                query: (data) => ({
                    url: `${IMAGES_API_URL}/upload/${data.field}`,
                    method: "PUT",
                    formData: true,
                    body: data.formData,
                    // credentials: "include",
                }),
                invalidatesTags: ["Images"],
            }),