1

I have the following form that is using react hook form.

What Am I supposed to pass to the API route via fetch such that I can write the image to disk with fs, and retrieve its location and pass that to Cloudinary? In the body of the fetch I can pass the picture object which is inside of data, but how does that give me enough info to write the image to disk with fs.

I want to pass the full image path to Cloudinary so it can upload the image, I am currently only passing the name which is wrong.

<form onSubmit={handleSubmit(onSubmit)}>
    <div>
    <h2>Image</h2>
    <input ref={register} type="file" name="picture" />
    </div>
    <div>
    <h2>Description</h2>
    <input
        type="text"
        placeholder="Description"
        name="description"
        ref={register({ required: true })}
    />
    </div>
    <div>
    <input type="submit" />
    </div>
</form>

This is the onSubmit function that is called

const onSubmit = async (data) => {
    console.log(data);
  

    console.log(data);

    const res = await fetch("../api/image", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(data.picture[0].name),
    }).then((ok) => {
      console.log(ok);
    });

  };

and this is the API route that it is hitting

var cloudinary = require("cloudinary").v2;
import { fs } from "fs";

// first we need to disable the default body-parser

cloudinary.config({
  cloud_name: process.env.CLOUDINARY_NAME,
  api_key: process.env.CLOUDINARY_API_KEY,
  api_secret: process.env.CLOUDINARY_API_SECRET,
});

export default async function image(req, res) {

  cloudinary.uploader.upload(`${req.body}`, function (error, result) {
  console.log(result, error);
  });

  try {
    // const result = req.body;
    res.status(200).send(req.body);
  } catch (error) {
    console.error(error);
    res.status(error.requestResult.statusCode).send(error.message);
  }

Here is what I have access to on the picture object in the data object

obj

UPDATE: I have tried the suggestion from this questionto no avail, it just returns null and two empty objects after the form.parse.

How to send an image file using formdata to React/Next js api?

client

const formData = new FormData();
    formData.append("image", data.picture[0]);

    const res = await fetch("../api/image", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(formData),
    }).then((ok) => {
      console.log(ok);

server

var cloudinary = require("cloudinary").v2;
import { fs } from "fs";
import formidable from "formidable";

// first we need to disable the default body parser
export const config = {
  api: {
    bodyParser: false,
  },
};

cloudinary.config({
  cloud_name: process.env.CLOUDINARY_NAME,
  api_key: process.env.CLOUDINARY_API_KEY,
  api_secret: process.env.CLOUDINARY_API_SECRET,
});

export default async function image(req, res) {
  const form = new formidable.IncomingForm();
  form.uploadDir = "./";
  form.keepExtensions = true;
  form.parse(req, (err, fields, files) => {
    console.log(err, fields, files);
  });

  //   cloudinary.uploader.upload(`${body}`, function (error, result) {
  //     console.log(result, error);
  //   });

  try {
    // const result = req.body;
    res.status(200).send(req.body);
  } catch (error) {
    console.error(error);
    res.status(error.requestResult.statusCode).send(error.message);
  }
}

UPDATE: So if I do the following the formData is an empty object but the console log is not, why is this, I think that is my problem.

formData.append("image", data.picture[0]);

    console.log(data.picture[0]);
    console.log(formData); // empty

so apparently you can't just console log formData, but it looks like you can do this, which is saying the File object is in there, so not sure why this is not working.

for (var key of formData.entries()) {
      console.log(key[0] + ", " + key[1]);
    }
Anders Kitson
  • 1,413
  • 6
  • 38
  • 98
  • 1
    `FormData` does not work like a *normal* JavaScript object. It was designed to do one specific thing and not to have any enumerable keys. That is why the consoles log `toString()` method is useless. But this one should work: `for (var [key, value] of formData.entries()) { console.log(key, value); }` – Gh05d Mar 12 '21 at 11:16

1 Answers1

2

I think you were on the right track, but you should remove the header from the upload in the client and I also don't think that you need to stringify it:

const formData = new FormData();
formData.append("image", data.picture[0]);

const res = await fetch("../api/image", {
  method: "POST",
  body: formData,
}).then(ok => {
  console.log(ok);
});

The reason why you should remove the header is this (quoting the mdn docs):

Warning: When using FormData to submit POST requests using XMLHttpRequest or the Fetch_API with the multipart/form-data Content-Type (e.g. when uploading Files and Blobs to the server), do not explicitly set the Content-Type header on the request. Doing so will prevent the browser from being able to set the Content-Type header with the boundary expression it will use to delimit form fields in the request body.

The reason why you can't console log the formData is because it does not have enumerable keys, so the toString() method of console.log is useless in that case. Quoting mdn again:

Enumerable properties show up in for...in loops unless the property's key is a Symbol

In order to log the properties of formData you have to do it like this:

for (var [key, value] of formData.entries()) {
  console.log(key, value);
}

Or checkout this thread for more possibilities.

Gh05d
  • 7,923
  • 7
  • 33
  • 64
  • 1
    OK I ended up using axios and that worked, but I will go back and test this approach and if it works come back and choose this answer, just to make sure. But I bet it's right, makes sense to me. Thanks – Anders Kitson Mar 12 '21 at 14:23