2
votes

FormData ne fonctionne pas sur les fichiers de téléchargement dans le projet React avec Redux et AXIOS lorsqu'il est déployé

J'ai un projet de pile MERN complet avec Redux et AXIOS. J'ai utilisé FormData pour télécharger les images sur mon serveur de nœuds qui a le multer et cela fonctionne parfaitement bien sur mon hôte local même la console sur mon chrome dit vide? (FormData {}). Lorsqu'il est déployé , mon FormData est vide. J'ai donc testé mon FormData sans les fichiers (juste la valeur d'entrée des formulaires) et il passe au serveur et l'a sur req.body.

J'ai essayé d'ajouter la configuration de mon formData et n'a pas fonctionné.

Qu'est-ce que je fais mal ???

Par exemple

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

Voici quelques-uns de mes codes:

REACT Formulaire JS

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

Mon FormAction.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....................");
    }
  );
  }
);

node.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
      })
    );
};

ERREURS / JOURNAUX sur mon PM2

0 | serveur | 2019-01-13 21:27 -07: 00: Le serveur est prêt à prendre des messages 0 | serveur | 2019-01-13 21:28 -07: 00: corps demandé !!!!! [Objet: null prototype] {} 0 | serveur | 2019-01-13 21:28 -07: 00: req.files !!!!! [] 0 | serveur | 2019-01-13 21:28 -07: 00: {[Erreur: ENOENT: pas de tel fichier ou répertoire, ouvrez '/ var / www / LCTW / uploads / file-1547440111023'] 0 | serveur | 2019-01-13 21:28 -07: 00: numéro d'erreur: -2, 0 | serveur | 2019-01-13 21:28 -07: 00: code: 'ENOENT', 0 | serveur | 13/01/2019 21:28 -07: 00: appel système: 'ouvert', 0 | serveur | 2019-01-13 21:28 -07: 00: chemin: '/ var / www / LCTW / uploads / file-1547440111023', 0 | serveur | 2019-01-13 21:28 -07: 00: storageErrors: []}

ici, mes fichiers req.body et req.files sont vides. MAIS

quand j'ai commenté des parties de fichiers sur mon node.js, req.body existe!

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);


0 commentaires

4 Réponses :


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

Le type de contenu multipart / form-data doit spécifier le paramètre boundary , que vous ne pouvez pas connaître à l'avance.

Ne remplacez pas le type de contenu qui sera défini automatiquement par XHR / fetch.


1 commentaires

J'ai essayé avec et sans. c'est pareil. dites-vous que je dois définir la limite?



2
votes

Je peux voir deux problèmes dans votre code

D'abord à partir de la page npm de analyseur de corps

Cela ne gère pas les corps en plusieurs parties, en raison de leur complexité et typiquement grande nature. Pour les corps en plusieurs parties, vous pourriez être intéressé par les modules suivants:

  1. busboy et connect-busboy
  2. multipartie et connexion-multipartite
  3. formidable
  4. multer

Donc, body-parser ne remplira pas le req.body mais puisque vous utilisez déjà multer voici un exemple sur comment remplir le req.body avec le multipart/form-data.

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());
  }
});

mais puisque vous avez besoin de fichiers et ce qui précède ne fonctionnera pas, vous pouvez utiliser upload.any()

Deuxièmement votre injection de middleware est dans le mauvais ordre.

Modifier ceci

global.rootPath = __dirname;

à

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

  );
  }
);

Et au lieu de

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

     //code

    }
  );
  }
);

faire

var upload = multer({
  storage: storage
})

MODIFIER 1

Ajouter dans app.js ou index.js ou le point de départ de votre application p>

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

global.rootPath aura désormais le chemin complet de votre application. ex / usr / user / Desktop / myapp en utilisant path, join (global.rootPath, "uploads") vous donnera / usr / user / Desktop / myapp / uploads . La bonne chose d'utiliser path.join est qu'il gère différents systèmes de chemin de système d'exploitation comme Windows et * nix

Utilisez toujours path.join pour créer tous les chemins.

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


7 commentaires

Merci beaucoup. Cela fonctionne un peu, maintenant j'ai de nouvelles erreurs sur mes journaux pm2, lorsque j'essaye de télécharger une image. 0 | serveur | 2019-01-14 02:56 -07: 00: Erreur: ENOENT: aucun fichier ou répertoire de ce type, ouvrez '/ var / www / LCTW / uploads / file-1547459776817' Comment se fait-il qu'il cherche ce fichier? n'est-il pas censé rechercher base64 ?


avec POST 500 ERROR


Est-ce quelque chose à voir avec mon var storage = multer.diskStorage ({destination: function (req, file, cb) {cb (null, __dirname + "../../../uploads"); / / vous indiquez où télécharger les fichiers,}, filename: function (req, file, cb) {cb (null, file.fieldname + "-" + Date.now ());}}); ?


@ I.Y Eh bien, vous faites fs.readFileSync (file.path); donc supposément si le fichier n'est pas là, vous devriez obtenir l'erreur ENOENT . Je vous suggère de toujours utiliser path.join pour créer des chemins !!. Je vous suggère également de faire quelque chose comme global.rootPath = __dirname à l'index.js et d'éviter d'utiliser ../../ .. . Idéalement, cela devrait ressembler à path, join (__ dirname, global.rootPath, "uploads")


comme ça dans mon server.js? app.use (path.join (__ dirname, global.rootPath, "uploads")); j'ai essayé d'avoir une mauvaise passerelle


Pas exactement. J'ai ajouté l'exemple dans la réponse. J'ai accidentellement fait une erreur de copier-coller dans le commentaire


son cb (null, path.join (global.rootPath, "uploads")) son "." Pas vrai? eh bien, j'ai essayé les deux et il y a une erreur en disant path.js: 28 throw new TypeError ('Path must be a string. Received' + inspect (path));



0
votes

J'ai utilisé avec succès FormData () dans mon propre code React pour télécharger des fichiers, mais pour une raison que je ne peux pas m'expliquer, les fichiers doivent être ajoutés en dernier. Je me demande si cela a à voir avec la réponse précédente mentionnant l'exigence d'un paramètre de limite et l'incapacité de le connaître jusqu'à ce que le téléchargement réel se produise.

Essayez d'abord d'ajouter vos données, puis les fichiers en dernier. Je voudrais également simplement essayer un seul fichier comme cas de test. Encore une fois, dernier.

Ajoutez-les d'abord:

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

Puis appelez ceci:

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

J'espère que cela vous aidera. Pour mémoire, j'utilise également multer, mais j'ai eu le même problème en utilisant multer côté serveur. L'ajout de données avant les fichiers était ma solution requise.

Cordialement,

DB


0 commentaires

0
votes

lorsque vous utilisez multer, votre image est stockée dans la propriété file de votre requête, donc req.file, mais vous avez req.files. Je ne sais pas si c'est votre seul problème, car je vois que d'autres ont commenté stackOverflow, mais je pense que c'est également un problème. Faites un console.log (req.file) et assurez-vous que j'ai raison, mais j'ai juste utilisé multer dans mon code aussi et le mien fonctionne.


0 commentaires