问题
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:
- busboy and connect-busboy
- multiparty and connect-multiparty
- formidable
- 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