FormData не работает при загрузке файлов в проекте React с Redux и AXIOS при его развертывании - PullRequest
0 голосов
/ 14 января 2019

У меня есть полный проект стека MERN с Redux и AXIOS. Я использовал FormData для загрузки изображений на сервер моего узла, на котором установлен мультитер, и он отлично работает на моем локальном хосте, даже если консоль на моем Chrome сказала пустым? (FormData {}). Когда развернуто , мои FormData пусты. Поэтому я проверил мои FormData без файлов (только входное значение из форм), и он передается на сервер и получает его в req.body.

Я пытался добавить config мои formData и не работал.

Что я делаю не так ???

Например

config: { headers: { 'Content-Type': 'multipart/form-data' } } и т.д .....

Вот некоторые из моих кодов:

РЕАКТ Форма 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....................");
    }
  );
  }
);

ОШИБКИ / ЛОГИ на моем PM2

0 | сервер | 2019-01-13 21:27 -07: 00: сервер готов принимать сообщения 0 | сервер | 2019-01-13 21:28 -07: 00: req.body !!!!! [Объект: ноль прототип] {} 0 | сервер | 2019-01-13 21:28 -07: 00: req.files !!!!! [] 0 | сервер | 2019-01-13 21:28 -07: 00: {[Ошибка: ENOENT: нет такого файл или каталог, откройте '/ var / www / LCTW / uploads / file-1547440111023'] 0 | сервер | 2019-01-13 21:28 -07: 00: errno: -2, 0 | сервер | 2019-01-13 21:28 -07: 00: код: 'ENOENT', 0 | сервер | 2019-01-13 21:28 -07: 00: системный вызов: 'open', 0 | сервер | 2019-01-13 21:28 -07: 00: путь: '/ var / www / LCTW / uploads / file-1547440111023', 0 | сервер | 2019-01-13 21:28 -07: 00: ошибки хранения: []}

здесь мои req.body и req.files пусты. НО

когда я закомментировал части файлов в моем node.js, req.body существует!

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' }

Ответы [ 4 ]

0 голосов
/ 16 января 2019

когда вы используете multer, ваше изображение сохраняется в свойстве file вашего запроса, поэтому req.file, но у вас есть req.files. Не уверен, что это ваша единственная проблема, так как я вижу, что другие прокомментировали stackOverflow, но я думаю, что это тоже проблема. Сделайте console.log (req.file) и убедитесь, что я прав, но я просто использовал multer в своем коде, и мой работает.

0 голосов
/ 14 января 2019

Я вижу две проблемы в вашем коде

Первый со страницы npm body-parser

Это не относится к составным телам из-за их сложных и типично большая природа. Для многочастных органов вас может заинтересовать следующие модули:

  1. Busboy и Connect-Busboy
  2. многопартийность и подключение-многопартийность
  3. грозным
  4. multer

Так что body-parser не будет заполнять req.body, но так как вы уже используете multer, вот пример того, как заполнить req.body с помощью multipart/form-data.

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

но так как вам нужны файлы и вышеперечисленное не работает, вы можете использовать upload.any()

Секунда Ваша инъекция промежуточного программного обеспечения находится в неправильном порядке.

Изменить это

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

до

var upload = multer({
  storage: storage
})

И вместо

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

     //code

    }
  );
  }
);

сделать

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

  );
  }
);

РЕДАКТИРОВАТЬ 1

Добавьте в app.js или index.js или в начальную точку вашего приложения

global.rootPath = __dirname;

global.rootPath теперь будет иметь полный путь к вашему приложению. бывший /usr/user/Desktop/myapp использование path,join(global.rootPath, "uploads") даст вам /usr/user/Desktop/myapp/uploads. Преимущество использования path.join заключается в том, что он обрабатывает различные пути ОС, такие как Windows и * nix

Всегда используйте path.join для создания всех путей.

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());
  }
});
0 голосов
/ 15 января 2019

Я успешно использовал FormData () в своем собственном коде React для загрузки файлов, но по какой-то причине, которую я не могу объяснить сам, файлы должны добавляться в последнюю очередь. Мне интересно, связано ли это с предыдущим ответом, в котором упоминается требование о граничном параметре и невозможность знать его, пока не произойдет фактическая загрузка.

Попробуйте сначала добавить свои данные, а затем файлы. Я также хотел бы просто попробовать один файл в качестве контрольного примера. Опять последний.

Сначала добавьте их:

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

Тогда назовите это:

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

Надеюсь, это поможет. Для записи я также использую multer, но у меня была такая же проблема при использовании multer на стороне сервера. Добавление данных до файлов было моим обязательным исправлением.

С уважением,

DB

0 голосов
/ 14 января 2019
config: { headers: { 'Content-Type': 'multipart/form-data' } } 

Тип содержимого multipart / form-data должен указать параметр boundary, который вы не можете знать заранее.

Не переопределяйте Content-Type, который будет автоматически установлен XHR / fetch.

...