FormData doesn't work on upload files in React project with Redux and AXIOS when Its deployed

五迷三道 提交于 2019-12-13 00:25:58

问题


I have a full MERN stack project with Redux and AXIOS. I used FormData to upload the images to my node server which has the multer and it works perfectly fine on my localhost even tho console on my chrome said empty? (FormData {}). When it's deployed, my FormData is empty. So I tested my FormData without the files(just the input value from forms) and it passes to the server and has it on req.body.

I tried to add config my formData and didn't work.

What am i doing wrong???

For Example

config: { headers: { 'Content-Type': 'multipart/form-data' } } etc.....

Here are some of my codes:

REACT Form JS

import React, { Component, Fragment } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import TextAreaFieldGroup from "../common/TextAreaFieldGroup";
import InputGroup from "../common/InputGroup";
import { addEventful, upload } from "../../actions/eventfulActions";

import Dropzone from "react-dropzone";

const imageMaxSize = 10000000
; //bytes
const acceptedFileTypes =
  "image/x-png, image/png, image/jpg, image/jpeg, image/gif";
const acceptedFileTypesArray = acceptedFileTypes.split(",").map(item => {
  return item.trim();
});

class EventfulForm extends Component {
  constructor(props) {
    super(props);
    this.state = {
      eventtitle: "",
      description: "",
      // comments:'',
      files: [],
      errors: {}
    };

    this.onChange = this.onChange.bind(this);
    this.onSubmit = this.onSubmit.bind(this);
  }


  componentWillReceiveProps(newProps) {
    if (newProps.errors) {
      this.setState({ errors: newProps.errors });
    }
  }

  verifyFile(files){
    if(files && files.length > 0){
      const currentFile = files[0]
      const currentFileType = currentFile.type
      const currentFileSize = currentFile.size
      if(currentFileSize > imageMaxSize){
        alert("TOO MANY FILES")
        return false
      }
      if (!acceptedFileTypesArray.includes(currentFileType)) {
        alert("IMAGES ONLY")
        return false
      }
      return true

    }
  }
  onSubmit(e) {
    e.preventDefault();
    const { user } = this.props.auth;



    const formdata = new FormData();
    this.state.files.forEach((file, i) => {
      const newFile = { uri: file, type: "image/jpg" };
      formdata.append("file", file, file.name);
    });

    // const newEventful = {
    //   eventtitle: this.state.eventtitle,
    //   description: this.state.description,
    //   pictures: this.state.pictures,
    //   name: user.name
    // };

    formdata.append("eventtitle", this.state.eventtitle);
    formdata.append("description", this.state.description);
    formdata.append("name", user.name);

    this.props.addEventful(formdata);
    this.setState({ eventtitle: "" });
    this.setState({ description: "" });
    this.setState({ files: [] });
  }
  onChange(e) {
    this.setState({ [e.target.name]: e.target.value });
  }

  onDrop = (files, rejectedFiles) => {
    if(rejectedFiles && rejectedFiles.length > 0){
      console.log(rejectedFiles)
      this.verifyFile(rejectedFiles)
    }
    if (files && files.length > 0) {
      const isVerified = this.verifyFile(files)
      if(isVerified){
        console.log(files[0].name);
        const formdata = new FormData();
        files.map(file => {
          formdata.append("file", file, file.name);
        });
        // formdata.append("file", files[0], files[0].name);

        console.log(formdata);
        // this.props.upload(formdata);
        this.setState({
          files: files
        });
      }
    }
  };

  render() {
    const previewStyle = {
      display: "inline",
      width: 100,
      height: 100
    };
    const { errors, files } = this.state;

    return (
      <div className="post-form mb-3">
        <div className="card card-info">
          <div className="card-header bg-info text-white">Create an Event</div>
          <div className="card-body">
            <form onSubmit={this.onSubmit}>
              <div className="form-group">
                <InputGroup
                  placeholder="Create a event title"
                  name="eventtitle"
                  value={this.state.eventtitle}
                  onChange={this.onChange}
                  error={errors.eventtitle}
                />
                {files.length > 0 && (
                  <Fragment>
                    <h3>Files name</h3>
                    {files.map((picture, i) => (
                      <p key={i}>{picture.name}</p>
                    ))}
                  </Fragment>
                )}
                <Dropzone
                  onDrop={this.onDrop.bind(this)}
                  accept={acceptedFileTypes}
                  maxSize={imageMaxSize}
                >
                  <div>
                    drop images here, or click to select images to upload.
                  </div>
                </Dropzone>


                <TextAreaFieldGroup
                  placeholder="Description"
                  name="description"
                  value={this.state.description}
                  onChange={this.onChange}
                  error={errors.description}
                />
              </div>
              <button type="submit" className="btn btn-dark">
                Submit
              </button>
            </form>
          </div>
        </div>
      </div>
    );
  }
}

EventfulForm.propTypes = {
  addEventful: PropTypes.func.isRequired,
  auth: PropTypes.object.isRequired,
  errors: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
  auth: state.auth,
  errors: state.errors,
  eventful: state.files
});

export default connect(
  mapStateToProps,
  { addEventful, upload }
)(EventfulForm);

My FormAction.js

import axios from "axios";

import {
  ADD_EVENTFUL,
  GET_ERRORS,
  ADD_LIKE,
  REMOVE_LIKE,
  GET_EVENTFUL,
  GET_EVENTFULS,
  DELETE_EVENTFUL,
  CLEAR_ERRORS,
  EVENTFUL_LOADING,
  UPLOAD_FILES
} from "./types";

const config = {
  onUploadProgress: progressEvent =>
    console.log(
      "Upload Progress" +
        Math.round((progressEvent.loaded / progressEvent.total) * 100) +
        "%"
    )
};
// Add eventful
export const addEventful = eventfulData => dispatch => {
  dispatch(clearErrors());
  // .post("/api/eventfuls", eventfulData, config)

  axios({
    method: 'post',
    url: '/api/eventfuls',
    data: eventfulData,
    config: { headers: { 'Content-Type': 'multipart/form-data' } }

  }).then(res =>
      dispatch({
        type: ADD_EVENTFUL,
        payload: res.data
      })
    )
    .catch(err =>
      dispatch({
        type: GET_ERRORS,
        payload: err.response.data
      })
    );
};

node.js

const express = require("express");
const router = express.Router();
const mongoose = require("mongoose");
const passport = require("passport");
const bodyParser = require("body-parser");

// Eventful model
const Eventful = require("../../models/Eventful");
const User = require("../../models/User");

// Validation
const validateEventfulInput = require("../../validation/eventful");
const validateCommentInput = require("../../validation/comment");

var multer = require("multer");

var fs = require("fs");
var path = require("path");

var btoa = require("btoa");

router.use(
  bodyParser.urlencoded({
    extended: false
  })
);
router.use(bodyParser.json());

var storage = multer.diskStorage({
  destination: function(req, file, cb) {
    cb(null, __dirname + "../../../uploads"); //you tell where to upload the files,
  },
  filename: function(req, file, cb) {
    cb(null, file.fieldname + "-" + Date.now());
  }
});

var upload = multer({
  storage: storage
}).array("file");

router.use((request, response, next) => {
  response.header("Access-Control-Allow-Origin", "*");
  response.header(
    "Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE, OPTIONS"
  );
  response.header("Access-Control-Allow-Headers", "Content-Type");
  next();
});

// @route   POST api/eventfuls
// @desc    Create eventful
// @access  Private
router.post(
  "/",
  passport.authenticate("jwt", { session: false }),
  (req, res) => {
    upload(req, res, err => {
      console.log("req.body!!!!!", req.body);
      const { errors, isValid } = validateEventfulInput(req.body);

      // Check Validation
      if (!isValid) {
        console.log(errors);
        // If any errors, send 400 with errors object
        return res.status(400).json(errors);
      }

      console.log("req.files!!!!!", req.files);
      if (err) {
        console.log(err);
        res.status(404).json({
          uploadFailed: "Upload failed"
        });
      } else {
        let newArr = [];

        for (let file of req.files) {
          let fileReadSync = fs.readFileSync(file.path);
          let item = {};
          item.image = {};
          item.image.data = fileReadSync;
          item.image.contentType = "img/png";
          newArr.push(item);

          fs.unlink(file.path, function(err) {
            if (err) {
              console.log("error deleting image", file.path);
            } else {
              console.log("deleted image", file.path);
            }
          });
        }
        for (var i = 0; i < newArr.length; i++) {
          var base64 = btoa(
            new Uint8Array(newArr[i].image.data).reduce(
              (data, byte) => data + String.fromCharCode(byte),
              ""
            )
          );
          newArr[i].image.data = base64;
        }

        console.log("33333333333333333333", newArr);

        const newEventful = new Eventful({
          title: req.body.eventtitle,
          description: req.body.description,
          pictures: newArr,
          user: req.user.id,
          name: req.user.name
        });

        newEventful.save().then(eventful => res.json(eventful));
      }
      console.log("skipped....................");
    }
  );
  }
);

ERRORS/ LOGS on my PM2

0|server | 2019-01-13 21:27 -07:00: Server is ready to take messages 0|server | 2019-01-13 21:28 -07:00: req.body!!!!! [Object: null prototype] {} 0|server | 2019-01-13 21:28 -07:00: req.files!!!!! [] 0|server | 2019-01-13 21:28 -07:00: { [Error: ENOENT: no such file or directory, open '/var/www/LCTW/uploads/file-1547440111023'] 0|server | 2019-01-13 21:28 -07:00: errno: -2, 0|server | 2019-01-13 21:28 -07:00: code: 'ENOENT', 0|server | 2019-01-13 21:28 -07:00: syscall: 'open', 0|server | 2019-01-13 21:28 -07:00: path: '/var/www/LCTW/uploads/file-1547440111023', 0|server | 2019-01-13 21:28 -07:00: storageErrors: [] }

on here my req.body and req.files are empty. BUT

when I commented out files parts out on my node.js, req.body exist!

0|server   | 2019-01-13 21:40 -07:00: req.body!!!!! [Object: null prototype] {
0|server   | 2019-01-13 21:40 -07:00:   eventtitle: 'asdfas',
0|server   | 2019-01-13 21:40 -07:00:   description: 'asdfads',
0|server   | 2019-01-13 21:40 -07:00:   name: 'In Soo Yang' }

回答1:


I can see two problems in you code

First from the npm page of body-parser

This does not handle multipart bodies, due to their complex and typically large nature. For multipart bodies, you may be interested in the following modules:

  1. busboy and connect-busboy
  2. multiparty and connect-multiparty
  3. formidable
  4. multer

So body-parser wont populate the req.body but since you are already using multer here is an example on how to populate the req.body with the multipart/form-data.

app.post('/', upload.none(), function (req, res, next) {
  // req.body contains the text fields
})

but since you need files and the above wont work you can use upload.any()

Second your middleware injection is in wrong order.

Change this

var upload = multer({
  storage: storage
}).array("file");

to

var upload = multer({
  storage: storage
})

And instead of

router.post(
  "/",
  passport.authenticate("jwt", { session: false }),
  (req, res) => {
    upload(req, res, err => {

     //code

    }
  );
  }
);

do

router.post(
  "/",
  passport.authenticate("jwt", { session: false }),
  upload.array("file"), //or upload.any()
  (req, res) => {

    //....code
    //now req.body sould work
    //file should be at req.files

  );
  }
);

EDIT 1

Add in app.js or index.js or the start point of you app

global.rootPath = __dirname;

global.rootPath will now have the full path to your app. ex /usr/user/Desktop/myapp using path,join(global.rootPath, "uploads") will give you /usr/user/Desktop/myapp/uploads. The good thing using path.join is that it handles diffrent OS path systems like Windows and *nix

Always use path.join to create all the paths.

var storage = multer.diskStorage({
  destination: function(req, file, cb) {
    cb(null, path.join(global.rootPath, "uploads")); //you tell where to upload the files,
  },
  filename: function(req, file, cb) {
    cb(null, file.fieldname + "-" + Date.now());
  }
});



回答2:


config: { headers: { 'Content-Type': 'multipart/form-data' } } 

The multipart/form-data content type must specify the boundary parameter, which you can't know in advance.

Do not override the Content-Type that will be set automatically by XHR / fetch.




回答3:


I have successfully used FormData() in my own React code to upload files, but for some reason that I cannot explain myself, the Files need to be appended last. I am wondering if this has to do with the prior reply mentioning the requirement of a boundary parameter and an inability to know it until the actual upload happens.

Try appending your data first, then the files last. I would also just try a single file as a test case. Again, last.

Append these first:

formdata.append("eventtitle", this.state.eventtitle);
formdata.append("description", this.state.description);
formdata.append("name", user.name);

Then call this:

this.state.files.forEach((file, i) => {
  const newFile = { uri: file, type: "image/jpg" };
  formdata.append("file", file, file.name);
});

Hope it helps. For the record, I also use multer, but I had the same issue while using multer on the server side. Appending data prior to files was my required fix.

Regards,

DB




回答4:


when you use multer your image is stored in the file property of your request, so req.file, but you have req.files. Not sure if that is your only problem as I see others have commented on stackOverflow, but I think that is an issue as well. Do a console.log(req.file) and make sure I'm right, but I just used multer in my code as well and mine works.



来源:https://stackoverflow.com/questions/54176118/formdata-doesnt-work-on-upload-files-in-react-project-with-redux-and-axios-when

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!