2

I want to save the images into the database uploaded from the React.js but it is not getting uploaded. It works fine in the postman but not in the React.js.

In the database I am saving only the file's path not the full data [Array] of the file.

The state: let [preview_image, setPreview_Image] = useState("")

The file handler function:

const uploadFileHandler = async (e) => {
    e.preventDefault();
    const file = e.target.files[0];   //The file logs the data about the file
    let formData = new FormData();
    let image = formData.append("preview_image", file);   //Here image logs the undefined in the console.
   // So, basically it is not appending the file-data to the formData
    setUploading(true);

    try {
      const config = {
        url: "http://localhost:8000/v1/template/create",
        method: "POST",
        data: formData,
        headers: {
          "Content-Type": "multipart/form-data",
          "x-access-token": adminInfo.data.JWToken,
        },
      };
      const { data } = await axios(config).catch(console.error);
      setPreview_Image(data);
      setUploading(false);
    } catch (error) {
      console.trace(error);
      setUploading(false);
    }
  };

The file uploader:

<Form.Group controlId="preview_image">
    <Form.Label>Image</Form.Label>
    <Form.Control
      type="text"
      placeholder="Upload Image url"
      value={preview_image}
      onChange={(e) => setPreview_Image(e.target.value)}
    ></Form.Control>
     <Form.Control
      type="file"
      label="Choose file"
      onChange={uploadFileHandler}
     ></Form.Control>
  </Form.Group>

The error: enter image description here

The template-creator action:

export const createtemplate = (templateData) => async (dispatch, getState) => {
  try {
    dispatch({ type: TEMPLATE_CREATE_REQUEST });

    const {
      adminLogin: { adminInfo },
    } = getState();

    const config = {
      url: "http://localhost:8000/v1/template/create",
      method: "POST",
      data: templateData,
      headers: {
        "Content-Type": "application/json",
        "x-access-token": adminInfo.data.JWToken,
      },
    };
    const { data } = await axios(config).catch(console.error);

    dispatch({ type: TEMPLATE_CREATE_SUCCESS, payload: data });
  } catch (error) {
    dispatch({
      type: TEMPLATE_CREATE_FAILURE,
      payload:
        error.response && error.response.data.message
          ? error.response.data.message
          : error.message,
    });
  }
};

The request and response from the postmanenter image description here

Not sure why I am getting the error.

The front-end: enter image description here

The templateScreen where I am calling the createtemplate action:

const submitHandler = (e) => {
    e.preventDefault();
    dispatch(
      createTemplate({
        name: templateName,
        package_ids: selectedPackage,
        html_template: htmlTemplate,
        required_variables: variables,
        preview_image: preview_image,
      })
    );
  };

BACKEND CONFIGURATION

The template creator service:

exports.createTemplate = async (req) => {
  // HERE req.files logs undefined after I click on the add template details button and req.body logs all the data.
  const name = req.body.name;
  const package_ids = req.body.package_ids;
  const html_template = req.body.html_template;
  const required_variables = req.body.required_variables;
  const preview_image = req.files.preview_image;

  const imagePath = preview_image.map((image) => image.path);

  const template = new Template({
    name,
    package_ids,
    html_template,
    required_variables,
    preview_image: imagePath.toString(),
  });
  await template.save();
  return template;
};

The controller file where the createTemplate service is being called

exports.createTemplate = catchAsync(async (req, res) => {
  try {
    const template = await templateService.createTemplate(req);
    return res.succeed(template, "Template created successfully");
  } catch (error) {
    console.trace(error.toJSON());
    return res.failed(500, "Internal Server Error", error);
  }
});

What it basically does is when I click on choose file and clicks on the files to upload it dispatches the create template action (that's what I think). Here it logs the files details and also responds with the following error from the backend's controller file: enter image description here

John Oliver
  • 125
  • 1
  • 11
  • How postman request differs from react on the wire? – Alex Blex Sep 24 '22 at 14:22
  • @AlexBlex Sorry I don't understand your question? – John Oliver Sep 25 '22 at 03:35
  • 1
    You said it work from postman but not from react. I ask how the HTTP request sent from react differs from the one you crafted in Postman. – Alex Blex Sep 25 '22 at 15:07
  • @AlexBlex They both are same. There is something wrong in my front-end code but don't know what and where? – John Oliver Sep 26 '22 at 01:56
  • Remove `"Content-Type": "multipart/form-data",` you [don't need it](https://stackoverflow.com/a/68643919/283366) and setting it manually often creates more problems – Phil Sep 26 '22 at 05:14
  • Please share the code or a link to the documentation for the `Form.Control` component – Phil Sep 26 '22 at 05:18
  • @Phil https://getbootstrap.com/docs/5.0/forms/form-control/#file-input and I removed the `"Content-Type": "multipart/form-data"` but no luck with that – John Oliver Sep 26 '22 at 05:24
  • 1
    That's native Bootstrap. Did you mean https://react-bootstrap.github.io/forms/form-control/? – Phil Sep 26 '22 at 05:25
  • @Phil Yes, actually. sorry, for the mistake – John Oliver Sep 26 '22 at 05:26
  • Could you please share a screenshot of the request in Postman? – Phil Sep 26 '22 at 05:39
  • @Phil Yes, I have updated the question. Please go through it – John Oliver Sep 26 '22 at 05:45
  • 1
    @JohnOliver, 500 is the serverside error. If nothing changed serverside identical requests should give identical response regardless of the client. The wire is the boundary of visibility for the server, so I ask you again, please confirm (browsers's devtoosl -> network tab- > copy request -> as curl vs postman's code snippet -> as curl) the requests you actually send to the server are the same. I agree something is wrong on your react code, and comparison of the requests is the first step to debug what exactly. – Alex Blex Sep 26 '22 at 07:55
  • @AlexBlex Yes you are right. The preview_image in the browser is ```[object FileList]``` Whereas in the postman it is different. But then what is the solution to this? (I imported the request in the postman from the browser and found the difference) – John Oliver Sep 26 '22 at 08:52
  • I am a bit surprised it was the only difference, but ok. The next step is to look at what your server accepts as a valid value for the preview_image. From the Postman screenshot it seems it is happy with the path to the uploaded image sent as a string. So I guess you suppose to upload the image first, then call this API providing the path. – Alex Blex Sep 26 '22 at 09:26
  • @AlexBlex Thanks. You helped me a lot to understand this. But can you show in the code what are the changes that I need to make. I understand that you are not here to do code for me but that help will be much appreciated. and what other differences are you talking about? If you are talking about headers like host origin or any other kind. yes, I didn't mention it or any other than that please let me know – John Oliver Sep 26 '22 at 09:42
  • 1
    frankly there are several grey areas which may need changes, but are not included in the question. E.g. `let formData = new FormData();` should return an empty form, and the only field you attach is the file. next, `let image = formData.append` should return undefined. `append` mutates the form itself. next, the `/v1/template/create` or any other endpoint should actually accept the file binary, not only file name. the fact that it works from postman suggests some changes are required serverside. All in all, find some step-through tutorial for react file uploading and vivisect each step. – Alex Blex Sep 26 '22 at 10:35
  • If your server responds with a 500 status, I would imagine it's also logging some sort of error. What do you see in the terminal where you start the Express server? Also, try `console.trace(error.toJSON());` for more details – Phil Sep 26 '22 at 23:26
  • Why do you have two methods of uploading? The one in `uploadFileHandler` looks right but where and how are you dispatching the `createtemplate` action? What are you passing in for `templateData`? – Phil Sep 26 '22 at 23:39
  • @Phil I have updated the question details. Please go through it. In the `templateData` I am sending the other details of the template like name, variables, html-template etc... – John Oliver Sep 27 '22 at 04:46
  • Check this out - https://stackoverflow.com/questions/35851660/multer-req-file-always-undefined. It seems the there is content-type issue in backend – Ankit Singh Oct 01 '22 at 09:18

3 Answers3

1

Another way is to convert image to base64 from the client and send the base64 string to the server.

The function to convert image file to base64 is

const toBase64 = file => new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = () => resolve(reader.result);
    reader.onerror = error => reject(error);
});


async function submit(){

// Testing the above code
const file = document.querySelector('#myfile').files[0];
const base64String = await toBase64(file);


const image = document.querySelector("#preview");
image.src = base64String;

const detail = document.querySelector("#detail");
detail.innerText = base64String;


}
<input  type="file"  id="myfile"/>

<button onclick="submit(this)"> Submit </button>

<div style="margin: 20px 0 20px 20px">
  <img id="preview" alt="the pic will come here" width="400" />
</div>

<p id="detail" style="width: 500px; word-wrap: break-word;"> </p>

The the server should take care for the rest, convert back into file or you can save it in the database as text.

In the image if you put src to that string it will work.

With this method you can always send the data as json in the body.

Feisal Ali
  • 351
  • 2
  • 9
0

In earlier versions of ExpressJS, files can be accessed easily as part of the request object using req.files. In recent versions, this property is no longer available on the request object. Now, you can use multer, a separate package, to access uploaded files. instantiate it by telling where to store all uploaded files And add it as a middleware

const fileUpload = require('../middleware/file-upload');

router.post( '/', fileUpload.single('image'), postControllers.createPost );

And in the submithandler your formdata is undefined when calling the data , let image = formData.append("preview_image", file); It should be assigned too the formData directly : formData.append("preview_image", file)

Sorry if i come across as a jerk, new too answering just practising to help

wijnieMen
  • 121
  • 1
  • 1
  • 6
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Sep 30 '22 at 12:52
0

uploadFileHandler callback will be invoked when the user selects the file. It'll call the API with the available form data. Is that the expected behaviour?

What I can see from your error logs:

  • API validates the form data. name and html_template is required.
  • Frontend does not do a form validation for name and html_template fields

Make sure you have values for above fields if you want to call the API as soon as user select the file.

LahiruK717
  • 47
  • 7