I'm facing a problem while uploading images from react state to express.
I'm getting empty array in my express route while sending images. But texts are sent and received in backend with no problem. The problem is that images are not sent to the backend.
This is my React code:
import React, { Component } from 'react'
import ImageUploader from 'react-images-upload'
import API from '../../../../api'
export default class AddCategory extends Component {
constructor(props) {
super(props);
this.state = {
formData: {
name_uz: '',
name_ru: '',
name_en: ''
},
pictures: [],
response: false,
loading: false,
responseText: '',
responseResult: false
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.clearForm = this.clearForm.bind(this);
this.onDrop = this.onDrop.bind(this);
}
onDrop(picture) {
this.setState({
pictures: this.state.pictures.concat(picture),
});
}
handleChange = e => {
let state = (this.state);
let a = state.formData;
a[e.target.name] = e.target.value;
this.setState({formData: a});
}
handleSubmit = e => {
e.preventDefault()
this.setState({ loading: true })
const texts = this.state.formData
const pictures = this.state.pictures
console.log(pictures)
const postData = {texts, pictures}
API.post(`/content/category/new`, {postData})
.then(res => {
this.setState({ responseText: res.data.text })
res.data.status ? this.setState({ loading: false, responseResult: true, response: true }) : this.setState({ loading: false, responseResult: false, response: true })
this.clearForm()
setTimeout(function () {
this.setState({response: false});
}.bind(this), 6900)
})
}
render() {
return (
<div className='content-manager-section'>
<Form onSubmit={this.handleSubmit}>
<ImageUploader
withIcon={true}
buttonText='Выберите изображения'
onChange={this.onDrop}
imgExtension={['.jpg']}
maxFileSize={5242880}
label='Максимальный размер изображения: 2 Мб, Тип изображения: .jpg'
fileSizeError='Файл слишком большой файл'
fileTypeError='Недопустимый тип файла'
singleImage={true}
/>
<input onChange={this.handleChange} value={this.state.formData.name_uz} maxLength='50' name='name_uz' placeholder='Название' />
<input onChange={this.handleChange} value={this.state.formData.name_ru} maxLength='50' name='name_ru' placeholder='Название' />
<input onChange={this.handleChange} value={this.state.formData.name_en} maxLength='50' name='name_en' placeholder='Название' />
<Button className={this.state.loading ? 'loading submit-btn' : 'submit-btn'} type='submit'><i className='lnil lnil-cloud-upload'></i>Добавить</Button>
</Form>
</div>
)
}
}
This is my Express code:
const uuidv4 = require('uuid')
const multer = require("multer")
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, DIR);
},
filename: (req, file, cb) => {
const fileName = file.originalname.toLowerCase().split(' ').join('-');
cb(null, uuidv4() + '-' + fileName)
}
});
var upload = multer({
storage: storage,
fileFilter: (req, file, cb) => {
if (file.mimetype == "image/png" || file.mimetype == "image/jpg" || file.mimetype == "image/jpeg") {
cb(null, true);
} else {
cb(null, false);
return cb(new Error('Only .png, .jpg and .jpeg format allowed!'));
}
}
});
.post('/new', upload.single('image'), (req, res) => {
console.log(req.body)
console.log(req.files)
})
This is what Express is logging in console
{
postData: {
texts: { name_uz: '', name_ru: '', name_en: '' },
pictures: [ {} ]
}
}
undefined
Please, let me know my wrong steps.
Edit
It seems that square brackets [ ] inside state has some strange behaviour. Everytime I use [ ] inside state and try to post it, the sent array is empty.
I've solved it myself. I had to use the FormData interface for sending files.
In case if someone needs it, here is the documentation.
And in Express I've used multer fields for receiving pictures and texts.
Example from my React code that works:
const formData = new FormData();
for (var i = 0; i < this.state.pictures.length; i++) {
formData.append('categorypic', this.state.pictures[i])
}
formData.append('texts', JSON.stringify(this.state.formTexts))
axios.post('your_url_here', formData)
.then(res => {
})
Related
i have my multer uploading part:
news.js
const express = require('express');
const router = require("express").Router();
const multer = require('multer');
const News = require('../models/news');
const mongoose = require('mongoose');
const DIR = './public/'
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, DIR)
},
filename: (req, file, cb) => {
const fileName = file.originalname.toLowerCase().split(' ').join('-')
cb(null, fileName)
},
})
//Mime Type Validation
var upload = multer({
storage: storage,
limits: {
fileSize: 1024 * 1024 * 5,
},
fileFilter: (req, file, cb) => {
if (
file.mimetype == 'image/png' ||
file.mimetype == 'image/jpg' ||
file.mimetype == 'image/jpeg'
) {
cb(null, true)
} else {
cb(null, false)
return cb(new Error('Only .png, .jpg and .jpeg format allowed!'))
}
},
})
router.post('/news', upload.array('photos', 4), (req, res, next) => {
const reqFiles = [];
const url = req.protocol + '://' + req.get('host')
for (var i = 0; i < req.files.length; i++) {
reqFiles.push(url + '/public/' + req.files[i].filename)
}
const news = new News({
_id: new mongoose.Types.ObjectId(),
title: req.body.title,
subtitle: req.body.subtitle,
text: req.body.text,
photos: reqFiles,
})
news
.save()
.then((result) => {
console.log(result)
res.status(201).json({
_id: result._id,
title: result.title,
subtitle: result.subtitle,
text: result.text,
photos: result.photos,
})
})
.catch((err) => {
console.log(err),
res.status(500).json({
error: err,
})
})
})
router.get('/', (req, res, next) => {
News.find().then((data) => {
res.status(200).json({
message: 'News retrieved successfully!',
news: data,
})
})
})
router.get('/:id', (req, res, next) => {
News.findById(req.params.id).then((data) => {
if (data) {
res.status(200).json(post)
} else {
res.status(404).json({
message: 'Not found!',
})
}
})
})
module.exports = router;
and my template with form and file input:
news-photo-uploader.component.html
<form
[formGroup]="form"
(ngSubmit)="submitForm()"
action="/news"
method="POST"
enctype="multipart/form-data">
<div class="form-group">
<input
type="file"
(change)="uploadFile($event)"
name="photos"
multiple/>
</div>
<div class="form-group">
<button type="submit">Add new post</button>
</div>
<!-- Image Preview -->
<div class="form-group">
<div class="preview" *ngIf="preview && preview !== null">
<img [src]="preview" [alt]="form.value.name" />
</div>
</div>
</form>
when i send POST request with Postman, everything works fine and i receive all (2) my image links
{
"_id": "63330b3d9b83959f4b65ba75",
"photos": [
"http://localhost:3000/public/2022-4-15_16-36-4.211.jpg",
"http://localhost:3000/public/img_20220301_210457.jpg"
]
}
but, when i try to do it with my Angular app
news.service.ts
import { News } from '../../data/interfaces/news';
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs';
import { HttpHeaders, HttpClient, HttpEvent } from '#angular/common/http';
import { ApiService } from 'src/app/shared/service/api.service';
#Injectable({
providedIn: 'root',
})
export class NewsService {
path: string = '/news';
constructor(private http: HttpClient, private apiService: ApiService) {}
// Get News
getNews(): Observable<{ news: News[] }> {
return this.apiService.get(this.path);
}
// Create News
addNews(title: string, subtitle: string, text: string, newsImages: Array<File>): Observable<HttpEvent<any>> {
var formData: any = new FormData();
formData.append('title', title);
formData.append('subtitle', subtitle);
formData.append('text', text);
// formData.append('photos', newsImages);
for(let i = 0; i < newsImages.length; i++){
formData.append('photos', newsImages[i]);
}
console.log(formData);
return this.http.post<News>(`${this.apiService.url}/news`, formData, {
reportProgress: true,
observe: 'events',
});
}
}
news-photo-uploader.component.ts
import { NewsService } from './../../news.service';
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup } from '#angular/forms';
import { HttpEvent, HttpEventType } from '#angular/common/http';
import { Router } from '#angular/router';
#Component({
selector: 'app-news-photo-uploader',
templateUrl: './news-photo-uploader.component.html',
styleUrls: ['./news-photo-uploader.component.scss'],
})
export class NewsPhotoUploaderComponent implements OnInit {
preview!: string;
form: FormGroup;
percentDone?: any = 0;
users: any[] = [];
constructor(public fb: FormBuilder, public router: Router, public newsService: NewsService) {
// Reactive Form
this.form = this.fb.group({
title: [''],
subtitle: [''],
text: [''],
photos: [null],
});
}
ngOnInit(): void {}
uploadFile(event: any) {
const file = (event.target as HTMLInputElement).files![0];
this.form.patchValue({
photos: file,
});
this.form.get('photos')!.updateValueAndValidity();
// File Preview
const reader = new FileReader();
reader.onload = () => {
this.preview = reader.result as string;
};
reader.readAsDataURL(file);
}
submitForm() {
this.newsService
.addNews(this.form.value.title, this.form.value.subtitle, this.form.value.text, this.form.value.photos)
.subscribe((event: HttpEvent<any>) => {
switch (event.type) {
case HttpEventType.Sent:
console.log('Request has been made!');
break;
case HttpEventType.ResponseHeader:
console.log('Response header has been received!');
break;
}
});
}
}
then all i receive in my console or DB is empty array instead list of links
photos:[]
subtitle:""
text:""
title:""
_id:"63330f199b83959f4b65ba7b"
what should i need to do in my news.service.ts to make it fixed?
I am trying to create upload profile image method that help user upload their profile picture on website but I am having trouble with I dont know how to send the image from client to server and make those image store on cloudinary or firebase.
My routes look like this:
ProfileAPI.js
const express = require("express");
const router = express.Router();
const { body, param } = require("express-validator");
const { catchErrors } = require("../errors/errorHandlers");
const multer = require('multer');
const uuidv4 = require('uuid/v4');
const upload_dir = './images';
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, upload_dir);
},
filename: (req, file, cb) => {
cb(null, `${uuidv4()}-${file.filename.toLowerCase}`);
}
});
const upload = multer({
storage: storage,
fileFilter: (req, file, cb) => {
if (
file.mimetype == 'image/png' ||
file.mimetype == 'image/jpg' ||
file.mimetype == 'image/jpeg'
) {
cb(null, true);
} else {
cb(null, false);
return cb(new Error('Only .png, .jpg and .jpeg format allowed!'));
}
}
});
const {
getUserProfile,
getUsersPublicProfile,
lookUpId,
updateUserProfile,
updateUserEmail,
deleteUserProfile,
// deleteUserSkill,
addPlayersProfile,
getCreatedPlayers,
updatePlayersProfile,
deleteUserCreatedPlayer,
} = require("./profilesController");
router.post(
"/upload",
upload.single('profileImg'),
updateUserProfile
);
So key points are the setup of storage which tells where to upload + the file filter in upload, right?
And the route.post which will `upload.single('profileImg'), right? the route will include my controller for updateUserProfile which can be found here:
profilesController.js
exports.updateUserProfile = async (req, res) => {
const userId = req.session.passport.user.id;
// This array will contain all the update functions to run.
const updates = [];
// If a gravatar url has not been generated, do it now.
const pictureValue = gravatar.url(
req.body.email,
{ s: "100", r: "pg", d: "retro" },
true
);
const payload = {
fullname: req.body.fullname,
location: req.body.location,
webpage: req.body.webpage,
linkedin: req.body.linkedin,
institution: req.body.institution,
bio: req.body.bio,
major: req.body.major,
mergedTo: userId,
picture: pictureValue,
skillone: req.body.skillone,
skilltwo: req.body.skilltwo,
skillthree: req.body.skillthree
};
}
So now to the frontend code (react.js):
This is the form I am loading in my react app:
UserProfile.js
const UserProfile = (serverUserData) => {
const appState = useContext(GlobalContext);
const { currentUser } = appState;
const { email, picture, name } = currentUser;
const [isVerified, setIsVerified] = useState(false);
const checkVerificationData = () => {
axios.get("/api/v1/profiles/profile").then((res) => {
const { data } = res;
if (data.verifiedDT) {
setIsVerified(data.verifiedDT);
}
});
};
useEffect(() => {
checkVerificationData();
}, [isVerified]);
// Upload user avatar function
const [imageSelected, setImageSelected] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
const formData = new FormData();
formData.append('email', email);
formData.append('name', name);
formData.append('profileImg', imageSelected);
axios
.post(`/upload`, formData)
.then(() => console.log("success"))
.catch(err => console.log(err));
};
const onFileChange = (e) => {
setImageSelected({ profileImg: e.target.files[0] });
};
};
const classes = useStyles();
return (
<div className={classes.root}>
<Grid item xs={12}
container
direction="row"
justify="center"
alignItems="center"
spacing={4}>
<Grid item>
<Grid item>
<UserCard
picture={currentUser.picture}
userEmail={email}
name={name}
isVerified={isVerified}
handleSubmit={handleSubmit}
onFileChange={onFileChange}
/>
<br />
</Grid>
and here is where user can upload their profile photo:
UserCard.js
{picture ? (
<div>
<Avatar
src={picture}
alt="Avatar"
className="avatar--profile_image"
/>
<input
type="file"
onChange={onFileChange}
/>
<button onClick={handleSubmit}>Submit</button>
</div>
) : (
<AccountCircleIcon className="avatar--profile_image" />
)}
So when entering things and hitting the Add Button my api states that req.file is undefined and I cannot find out why.
Can anyone help me drilling down the error?
To upload files you need to use contentType: "multipart/form-data".
Use the following as a reference to achieve the file upload.
helper function to create a instance with requied header. You may add any others to here.
const getInstance = () => {
return axios.create({
headers: {
"Content-Type": "multipart/form-data",
},
});
}
call this method with the file to be uploaded
const fileUplaod = (file) => {
let formData = new FormData();
formData.append("images", file, file.name);
getInstance()
.post(endpoint_post_url, formData)
.then((response) => {
console.log("IMAGES_SUBMIT_SUCCESS");
})
.catch((err) => {
console.error("image submit error", err);
});
}
check the request body in your backend code. You can upload multiple images as well to the same property. It will be an array in the request object.
Edit the handleSubmit function to add a config to the axios call.
const handleSubmit = (e) => {
e.preventDefault();
const formData = new FormData();
formData.append('email', email);
formData.append('name', name);
formData.append('profileImg', imageSelected);
axios
.post(`/upload`, formData, {
headers: {
'Content-Type': "multipart/form-data"
}
})
.then(() => console.log("success"))
.catch(err => console.log(err));
};
I am trying to create upload profile image method that help user upload their profile picture on website but I am having trouble with I dont know how to send the image from client to server and make those image store on cloudinary or firebase.
My routes look like this:
ProfileAPI.js
const express = require("express");
const router = express.Router();
const { body, param } = require("express-validator");
const { catchErrors } = require("../errors/errorHandlers");
const multer = require('multer');
const uuidv4 = require('uuid/v4');
const upload_dir = './images';
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, upload_dir);
},
filename: (req, file, cb) => {
cb(null, `${uuidv4()}-${file.filename.toLowerCase}`);
}
});
const upload = multer({
storage: storage,
fileFilter: (req, file, cb) => {
if (
file.mimetype == 'image/png' ||
file.mimetype == 'image/jpg' ||
file.mimetype == 'image/jpeg'
) {
cb(null, true);
} else {
cb(null, false);
return cb(new Error('Only .png, .jpg and .jpeg format allowed!'));
}
}
});
const {
getUserProfile,
getUsersPublicProfile,
lookUpId,
updateUserProfile,
updateUserEmail,
deleteUserProfile,
// deleteUserSkill,
addPlayersProfile,
getCreatedPlayers,
updatePlayersProfile,
deleteUserCreatedPlayer,
} = require("./profilesController");
router.post(
"/upload",
upload.single('profileImg'),
updateUserProfile
);
So key points are the setup of storage which tells where to upload + the file filter in upload, right?
And the route.post which will `upload.single('profileImg'), right? the route will include my controller for updateUserProfile which can be found here:
profilesController.js
exports.updateUserProfile = async (req, res) => {
const userId = req.session.passport.user.id;
// This array will contain all the update functions to run.
const updates = [];
// If a gravatar url has not been generated, do it now.
const pictureValue = gravatar.url(
req.body.email,
{ s: "100", r: "pg", d: "retro" },
true
);
const payload = {
fullname: req.body.fullname,
location: req.body.location,
webpage: req.body.webpage,
linkedin: req.body.linkedin,
institution: req.body.institution,
bio: req.body.bio,
major: req.body.major,
mergedTo: userId,
picture: pictureValue,
skillone: req.body.skillone,
skilltwo: req.body.skilltwo,
skillthree: req.body.skillthree
};
}
So now to the frontend code (react.js):
This is the form I am loading in my react app:
UserProfile.js
const UserProfile = (serverUserData) => {
const appState = useContext(GlobalContext);
const { currentUser } = appState;
const { email, picture, name } = currentUser;
const [isVerified, setIsVerified] = useState(false);
const checkVerificationData = () => {
axios.get("/api/v1/profiles/profile").then((res) => {
const { data } = res;
if (data.verifiedDT) {
setIsVerified(data.verifiedDT);
}
});
};
useEffect(() => {
checkVerificationData();
}, [isVerified]);
// Upload user avatar function
const [imageSelected, setImageSelected] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
const formData = new FormData();
formData.append('email', email);
formData.append('name', name);
formData.append('profileImg', imageSelected);
axios
.post(`/upload`, formData)
.then(() => console.log("success"))
.catch(err => console.log(err));
};
const onFileChange = (e) => {
setImageSelected({ profileImg: e.target.files[0] });
};
};
const classes = useStyles();
return (
<div className={classes.root}>
<Grid item xs={12}
container
direction="row"
justify="center"
alignItems="center"
spacing={4}>
<Grid item>
<Grid item>
<UserCard
picture={currentUser.picture}
userEmail={email}
name={name}
isVerified={isVerified}
handleSubmit={handleSubmit}
onFileChange={onFileChange}
/>
<br />
</Grid>
and here is where user can upload their profile photo:
UserCard.js
{picture ? (
<div>
<Avatar
src={picture}
alt="Avatar"
className="avatar--profile_image"
/>
<input
type="file"
onChange={onFileChange}
/>
<button onClick={handleSubmit}>Submit</button>
</div>
) : (
<AccountCircleIcon className="avatar--profile_image" />
)}
So when entering things and hitting the Add Button my api states that req.file is undefined and I cannot find out why.
Can anyone help me drilling down the error?
To upload files you need to use contentType: "multipart/form-data".
Use the following as a reference to achieve the file upload.
helper function to create a instance with requied header. You may add any others to here.
const getInstance = () => {
return axios.create({
headers: {
"Content-Type": "multipart/form-data",
},
});
}
call this method with the file to be uploaded
const fileUplaod = (file) => {
let formData = new FormData();
formData.append("images", file, file.name);
getInstance()
.post(endpoint_post_url, formData)
.then((response) => {
console.log("IMAGES_SUBMIT_SUCCESS");
})
.catch((err) => {
console.error("image submit error", err);
});
}
check the request body in your backend code. You can upload multiple images as well to the same property. It will be an array in the request object.
Edit the handleSubmit function to add a config to the axios call.
const handleSubmit = (e) => {
e.preventDefault();
const formData = new FormData();
formData.append('email', email);
formData.append('name', name);
formData.append('profileImg', imageSelected);
axios
.post(`/upload`, formData, {
headers: {
'Content-Type': "multipart/form-data"
}
})
.then(() => console.log("success"))
.catch(err => console.log(err));
};
Depending on your viewpoint, I was able to get the most important part of getting a Avatar/Image in an app...pushing to the database (MongoDB in my case, Also the image gets added to my server folder).
However what I can't get working is the success modal to fire or the rendering of the image on the page, which is strange as the response.status === 200 and response.ok === true? I am using Multer in Express. (I am using fetch).
This is my ImageLoader component:
import React, { Component } from 'react';
import './ImageUploader.css';
import Modal from '../Modal/MyModal.jsx'
import DefaultImg from '../../static/profile-avatars/assets/default-img.jpg';
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { modalStateOn, modalStateOff } from '../../store/index'
const API_URL = "http://localhost:8016";
class ImageUploader extends Component {
constructor(props) {
super(props);
this.state = {
multerImage: DefaultImg,
}
}
setDefaultImage(uploadType) {
if (uploadType === "multer") {
this.setState({
multerImage: DefaultImg
});
}
}
uploadImage(e, method) {
var { history, isLoggedIn, modalActive, modalStateOn, modalStateOff } = this.props
let imageObj = {};
if (method === "multer") {
let imageFormObj = new FormData();
imageFormObj.append("imageName", "multer-image-" + Date.now());
imageFormObj.append("imageData", e.target.files[0]);
// stores a readable instance of
// the image being uploaded using multer
this.setState({
multerImage: window.URL.createObjectURL(e.target.files[0])
});
return window.fetch('http://localhost:8016/images/uploadmulter', {
method: 'POST',
// body: e.target.files[0]
body: imageFormObj
})
.then((response) => {
console.log("response ", response);
this.setDefaultImage("multer");
return (
<>
{response.ok && <Modal
isAlertModal={true}
history={history}
affirmativeUsed="Yes"
message="Image has been successfully uploaded!"
isLoggedIn={isLoggedIn}
modalActive={true}
modalStateOn={modalStateOn}
modalStateOff={modalStateOff}></Modal>}
</>
)
})
.then((data) => {
console.log("data ", data);
this.setDefaultImage("multer");
})
.catch(error => {
this.setDefaultImage("multer");
console.log("error ", error);
})
}
} // end upload function
render() {
// console.log("uploadImage function this.props ", this.props);
return (
<div className="main-container">
<h3 className="main-heading">Image Upload App</h3>
<div className="image-container">
<div className="process">
<h4 className="process__heading">Process: Using Multer</h4>
<p className="process__details">Upload image to a node server, connected to a MongoDB database, with the help of multer</p>
<form action="/uploadmulter" method="post" encType="multipart/form-data" >
<input type="file" name="avatar" className="process__upload-btn" onChange={(e) => this.uploadImage(e, "multer")} />
<img src={this.state.multerImage} alt="upload-image" className="process__image" />
</form>
</div>
</div>
</div>
);
}
}
function mapStateToProps(state) {
const { isLoggedIn, modalActive } = state
return { isLoggedIn, modalActive }
}
const mapDispatchToProps = dispatch =>
bindActionCreators({ modalStateOn, modalStateOff }, dispatch)
export default connect(mapStateToProps, mapDispatchToProps)(ImageUploader)
And this is the route on my express server server/images/index.js:
var router = require('express').Router();
var Image = require('../models/Image');
var multer = require('multer')
var storage = multer.diskStorage({
destination: function(req, file, cb){
cb(null, './server/uploads/');
},
filename: function(req, file, cb){
cb(null, Date.now() + file.originalname)
}
})
var fileFilter = (req, file, cb) => {
if (file.mimetype === 'image/jpeg' || file.mimetype === 'image/png') {
cb(null, true);
} else {
// rejects storing a file
cb(null, false)
}
}
var upload = multer({
storage: storage,
limits : {
fileSize : 1024 * 1024 * 5
},
fileFilter: fileFilter
})
router.route('/uploadmulter')
.post(upload.single('imageData'), (req, res, next) => {
console.log('req.body', req.body);
var newImage = new Image({
imageName: req.body.imageName,
imageData : req.file.path
})
newImage.save()
.then(result => {
res.status(200).json({
success: true,
document:result
})
})
.catch(err=> next(err))
});
module.exports = router
So to reiterate why doesn't the image render and modal fire when the response is true or 200?
I'm trying to send a file/image from React to node.js server with multer. The problem is I can send an image only through Postman but when I'm trying the same in React I'm getting: TypeError: Cannot read property 'path' of undefined. I don't understand whether I should send a file/image in binary or use another format. I've already tried to use reader.readAsDataURL() and reader.readAsBinaryString() but it didn't work.
const multer = require("multer");
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, "./uploads/");
},
filename: (req, file, cb) => {
cb(null, new Date().toISOString() + file.originalname);
}
});
const fileFilter = (req, file, cb) => {
if (
file.mimetype === "image/jpeg" ||
file.mimetype === "image/png" ||
file.mimetype === "image/jpg"
) {
cb(null, true);
}
cb(null, false);
};
const upload = multer({
storage: storage,
limits: { fileSize: 5000000 },
fileFilter: fileFilter
});
// Create a post
router.post(
"/",
upload.single("image"),
passport.authenticate("jwt", { session: false }),
(req, res) => {
const { errors, isValid } = validationPostInput(req.body);
if (!isValid) {
return res.status(400).json(errors);
}
console.log(req.file);
const newPost = new Post({
text: req.body.text,
theme: req.body.theme,
name: req.body.name,
avatar: req.body.avatar,
image: req.file.path,
user: req.user.id
});
newPost.save().then(post => res.json(post));
}
);
// CREATE A POST
export const createPost = (userInput, history) => dispatch => {
const headers = {
"Content-Type": "form-data"
};
axios
.post("/post", userInput, headers)
.then(res => history.push("/post/all"))
.catch(err =>
dispatch({
type: GET_ERRORS,
payload: err.response.data
})
);
};
import React, { Component } from "react";
import PropTypes from "prop-types";
// import { Link } from "react-router-dom";
import { connect } from "react-redux";
import { createPost } from "../../actions/postActions";
import "./style.css";
class CreatePost extends Component {
state = {
text: "",
theme: "",
image: "",
errors: {}
};
componentWillReceiveProps(nextProps) {
if (nextProps.errors) {
this.setState({ errors: nextProps.errors });
}
}
onSubmit = e => {
e.preventDefault();
const { user } = this.props.auth;
const newPost = {
text: this.state.text,
theme: this.state.theme,
image: this.state.image,
name: user.username,
avatar: user.avatar
};
this.props.createPost(newPost);
};
onChange = e => this.setState({ [e.target.name]: e.target.value });
fileSelectHandler = e => {
const param = e.target.files[0];
let reader = new FileReader();
reader.readAsDataURL(param);
this.setState({
image: reader.result
});
console.log(reader);
};
render() {
const { text, theme, errors } = this.state;
return (
<section className="post">
<form
onSubmit={this.onSubmit}
className="post__form"
action="/post"
method="POST"
encType="multipart/form-data"
>
<div className="post__form--input">
<label>Theme</label>
<input
type="text"
name="theme"
value={theme}
onChange={this.onChange}
/>
{errors && <small>{errors.theme}</small>}
</div>
<div className="post__form--input">
<label>Text</label>
<textarea
type="text"
name="text"
value={text}
onChange={this.onChange}
/>
{errors && <small>{errors.text}</small>}
</div>
<div className="post__form--file">
<label>Add Image</label>
<input
type="file"
name="file"
accept=".png, .jpg"
onChange={this.fileSelectHandler}
/>
</div>
<button type="submit" className="button">
Submit
</button>
</form>
</section>
);
}
}
CreatePost.propTypes = {
errors: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
errors: state.errors,
auth: state.auth
});
export default connect(
mapStateToProps,
{ createPost }
)(CreatePost);
Finally, I found out how to solve that problem.
onSubmit = e => {
e.preventDefault();
const { user } = this.props.auth;
const form = new FormData();
form.append("file", this.state.file);
form.append("theme", this.state.theme);
form.append("text", this.state.text);
form.append("name", user.username);
form.append("avatar", user.avatar);
this.props.createPost(form);
}
You are sending the field name as file:
<input
type="file"
name="file"
accept=".png, .jpg"
onChange={this.fileSelectHandler}
/>
But you are setting the field name as "image" on multer:
upload.single("image")
You need to change it to:
upload.single("file")
The correct syntax is:
.single(fieldname)
Accept a single file with the name fieldname. The single file will be
stored in req.file.