React Hooks and Axios Image Upload with Multer - node.js

I am trying to add the ability to upload an image and then update a user object with that image path. When I upload the image using Insomnia client and send the request, I am able to successfully add the image and path to MongoDB. NodeJS user route below:
const express = require ("express");
const router = express.Router();
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const Users = require("../models/users");
const auth = require("../middleware/auth");
const multer = require('multer');
const storage = multer.diskStorage({
destination: function(req, file, cb) {
cb(null, './uploads/');
},
filename: function(req, file, cb){
cb(null, file.originalname);
}
});
const fileFilter = (req, file, cb) => {
// reject a file
if (file.mimetype === 'image/jpeg' || file.mimetype === 'image/png' || file.mimetype == "image/jpg") {
cb(null, true);
} else {
cb(null, false);
return cb(new Error('Only .png, .jpg and .jpeg format allowed!'));
}
};
const upload = multer({storage: storage, limits: {
fileSize: 1024 * 1024 * 5
},
fileFilter: fileFilter
});
// REQUEST TO FIND USER BY ID AND ADD A PROFILE PICTURE
router.put('/update/:id', upload.single('userImage'), (req, res) => {
Users.findById(req.params.id)
.then(user => {
user.userImage = req.file.path || user.userImage,
user
.save()
.then(() => res.json("The User is UPDATED succesfully!"))
.catch(err => res.status(400).json(`Error: ${err}`));
})
.catch(err => res.status(500).json(`Error: ${err}`));
console.log(req.body);
console.log(req.file);
});
However, when I test the code on the client I receive the following 500 error: ""Error: TypeError: Cannot read property 'path' of undefined." I am using React Hooks and Axios, code below:
import React, { useState, useContext } from 'react';
import Exit from '../cancel.svg';
import Modal from 'react-modal';
import { useForm } from "react-hook-form";
import axios from 'axios';
import UserContext from "../context/UserContext";
import { useHistory } from "react-router-dom";
Modal.setAppElement('#root');
const ProfilePicture = () => {
const [modalIsOpen, setModalIsOpen]= useState (true);
const { register, handleSubmit } = useForm ();
const [userImage, setUserImage] = useState();
const onSubmit = (data) => console.log(data);
const { userData } = useContext(UserContext);
const history = useHistory();
const changeOnClick = e => {
const users = {
userImage
};
console.log(users);
axios
.put(`http://localhost:5000/users/update/${userData.user.id}`, users)
.then(res => console.log(res.body))
.catch(err => {
console.log(err);
});
history.push("/Landing")
}
return(
<Modal isOpen={modalIsOpen} onRequestClose={() => setModalIsOpen(false)} className='finishBorder'>
<div className='border-bottom-two'>
<img onClick= {() => setModalIsOpen(false)} className='newexitButton'src={Exit} alt='X' />
<span className='lastThing'>One last thing! Add a profile picture</span>
</div>
<form onSubmit={handleSubmit(changeOnClick)} encType="multipart/form-data" >
<div>
<span className="dot"></span>
<input onChange={e => setUserImage(e.target.value)} ref = {register} type='file' name='userImage' className='picUploader'/>
{/* <button>submit</button> */}
</div>
<div>
<button type='submit' className='doneButton'>Done</button>
<div>
<span className='laterTwo'>I'll do this later</span>
</div>
</div>
</form>
</Modal>
)
}
export default ProfilePicture;
Appreciate the help!

What you are sending from the client is a "fakepath", not a file. To access the file from the input use:
setUserImage(e.target.files[0])
Also, I think multer only works with multipart/form-data, so you should check that, but is as easy as:
const formData = new FormData();
formData.append([image name], [image file]);
axios.post(`http://localhost:5000/users/update/${userData.user.id}`, formData, {
headers: { "Content-Type": "multipart/form-data" }})

Related

MulterError: Unexpected field error while uploading image from React js

All the answers related this error is directing towards checking the name upload.single("image") and the name attribute of the file input in client side, which is in my case same as "image" as multer. But still it is giving the error.
Following is the node js code:
const Imagestorage = multer.memoryStorage()
const upload = multer({ storage: Imagestorage })
app.post("/newpost", upload.single("image"), async(req, res) => {
console.log(req.body);
console.log(req.file);
let data={}
// convert base64 image data to string using datauri/parser, upload to cloudinary and send response
const extName = path.extname(req.file.originalname).toString();
const file64 = parser.format(extName, req.file.buffer);
const filename=file64.content
cloudinary.uploader.upload(filename, async(error, result) => {
if (error) {
res.status(500).send("error in uploading file to cloudinary"+error);
} else {
// result.secure_url is the URL of the uploaded file on Cloudinary
console.log(result.secure_url);
let Imageurl=await result.secure_url
data={
name: req.body.name,
location:req.body.location,
likes:req.body.likes,
description:req.body.description,
image:Imageurl
}
console.log(data)
let postedData=await postsModel.create(data)
res.json({
status:"ok",
postedData
})
}
});
});
//error field in case something happens with multer
// app.use((error, req, res, next) => {
// console.log('This is the rejected field ->', error.field);
// });
app.get("*", (req, res) => {
res.status(404).send("PAGE IS NOT FOUND");
})
Frontend code-
import axios from "axios";
import { useNavigate } from "react-router-dom";
const Form = () => {
const navigate = useNavigate();
function handleSubmit(event) {
event.preventDefault();
const formData = new FormData(event.target);
// Append the file input to the form data
const imageFile = formData.get("image");
formData.append("image", imageFile);
// Use Axios to send a POST request to your server with the form data
axios
.post("https://instabackend-gcwk.onrender.com/newpost", formData, {
//.post("http://127.0.0.1:5000/newpost", formData, {
headers: {
"Content-Type": "multipart/form-data"
}
})
.then((response) => {
console.log(response);
})
.catch((error) => {
console.error(error);
})
.finally(navigate("/insta"));
}
return (
<div>
<form onSubmit={handleSubmit}>
<label htmlFor="image">Image:</label>
<input type="file" id="image" name="image" accept="image/*" />
<button type="submit">Submit</button>
</form>
<button onClick={() => navigate(-1)}>Go Back Home</button>
</div>
);
};
export default Form;
When i tried -
app.use((error, req, res, next) => {
console.log('This is the rejected field ->', error.field);
});
it's giving error field as "This is the rejected field -> image "
Note: There is no issue in fetching the data
Replacing append with set of FormData() is making the code work. Javascript.info explains the working of formData() here
import axios from "axios";
const App = () => {
function handleSubmit(event) {
event.preventDefault();
const formData = new FormData(event.target);
//get image
const imageFile = formData.get("image");
//set image
formData.set("image", imageFile);
axios
.post("https://instabackend-gcwk.onrender.com/newpost", formData, {
headers: {
"Content-Type": "multipart/form-data"
}
})
.then((response) => {
console.log(response);
})
.catch((error) => {
console.error(error);
});
}
return (
<div>
<form onSubmit={handleSubmit}>
<label htmlFor="image">Image:</label>
<input type="file" id="image" name="image" accept="image/*" />
<button type="submit">Submit</button>
</form>
</div>
);
};
export default App;

Not getting image file in nodejs after posting from react

I'm sending the image file from react to nodejs. I used the SetImage function where the image file selected by the user is set to the 'myimage' state variable and from handleSubmit I'm posting the images to the '/blog/posts' endpoint using Axios. the image file is getting loaded on the 'myimage' state variable but as I posted using the Axios, I can't see all the data of the image file and giving me the empty object.
This is my React code:-
import React, { useState, useRef } from "react";
import './postblog.css';
import axios from '../axios';
const PostBlog = () => {
const [myimage,setImage] = useState("");
const handleSubmit = (event) => {
event.preventDefault();
axios.post('/blog/posts', {
image:myimage
}).then((res) => {
console.log(res.body);
console.log('successfully posted');
});
}
const SetImage = (e)=>{
console.log(e.target.files[0]);
setImage(e.target.files[0]);
console.log(myimage);
}
return (
<div className="postblog">
<form onSubmit={handleSubmit} enctype="multipart/form-data">
<input type="file" placeholder="Choose your file" onChange={(e)=>{SetImage(e)}} name="myImage"/>
</div>
<button type="submit">Post</button>
</form>
</div>
);
}
export default PostBlog;
This is my Nodejs code:-
var storage = multer.diskStorage({
destination: function(req, res, cb) {
cb(null, './Routers/blog/uploads');
},
filename: function(req, file, cb) {
cb(null, Date.now() + file.originalname);
}
});
var upload = multer({
storage: storage,
limits: {
fieldsize: 1024 * 1024 * 3
}
});
blog.post('/posts', upload.single('myimage'), (req, res, next) => {
console.log(req.body);
console.log(details);
})
The output in the console I'm getting is an empty object
You should send file with content type as multipart/form-data but in your code you are sending file as application/json content type.
...
const handleSubmit = (event) => {
event.preventDefault();
const formData = new FormData();
formData.append('file', myimage);
const config = {
headers: {
'content-type': 'multipart/form-data'
}
};
axios.post('/blog/posts', formData, config).then((res) => {
console.log(res.body);
console.log('successfully posted');
});
}
...

Why does my DataForm not return as an object that my URL.createObjectURL is supposed to read?

All I am trying to accomplish is to give my users the option to upload images. I decided to use mongoDB as my database which means I must store photos locally and then send them to the DB. As far as I know, I am new. The object created by ImageName and ImageData isn't being passed properly to my axios post request
.post(`http://localhost:5000/api/image/uploadmulter/`, imageFormObj)
.then((data) => {
if (data.data.success) {
alert("Image has been successfully uploaded using multer");
this.setDefaultImage("multerImage");
}
})
Here is my route
const Image = require('../models/imageModel');
const ImageRouter = express.Router();
const multer = require('multer');
const storage = multer.diskStorage({
destination: function (req, file, cb){
cb(null, './uploads/')
},
filename: function(req,file,cb){
cb(null, Date.now() + file.originalname);
}
});
const fileFilter = (req, file, cb) =>{
if(file.mimetype === 'image/jpeg' || file.mimetype ==='image/png'){
cb(null, true);
} else{
//rejects storing file
cb(null, false);
}
};
const upload = multer({
storage: storage,
limits:{
fileSize:1024 *1024 * 5
},
fileFilter: fileFilter
});
// stores image in uploads folder using multers and creates a reference to the file
ImageRouter.route("/upload")
.post(upload.single('imageData'), (req, res, next) => {
console.log(req.body);
const newImage = new Image({
imageName: req.body.imageName,
imageData: req.file.path
});
newImage.save()
.then((result)=>{
console.log(result)
res.status(200).json({
success: true,
document: result
});
})
.catch((err)=> next(err))
});
module.exports = ImageRouter;
here is my model
const Schema = mongoose.Schema;
const ImageSchema = new Schema({
imageName:{
type: String,
default: "none",
required: true
},
imageData: {
type :String,
required: false
}
});
const Image = mongoose.model('Image' , ImageSchema)
module.exports = Image;
Here is my ImageUploader page that calls the function
import axios from 'axios';
import DefaultImg from '../assets/default-img.jpg';
import 'bootstrap/dist/css/bootstrap.min.css';
export default class ImageUploader extends Component {
constructor(props) {
super(props)
this.state = {
multerImage: DefaultImg
}
};
setDefaultImage = (uploadType) => {
if (uploadType === "multer") {
this.setState({multerImage: DefaultImg});
};
};
// function to upload image once it has been captured include multer and
// firebase methods
uploadImage(e, method) {
let imageObj = {};
if (method === "multer") {
let imageFormObj = new FormData();
imageFormObj.append("imageName", "multer-image-" + Date.now());
imageFormObj.append("imageData", e.target.files[0]);
console.log(imageFormObj)
// stores a readable instance of the image being uploaded using multer
this.setState({
multerImage: URL.createObjectURL(e.target.files[0])
});
axios
.post(`http://localhost:5000/api/image/uploadmulter/`, imageFormObj)
.then((data) => {
if (data.data.success) {
alert("Image has been successfully uploaded using multer");
this.setDefaultImage("multerImage");
}
})
.catch((err) => {
alert("Error while uploading image using multer");
this.setDefaultImage("multer");
});
}
};
render() {
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>
<input
type="file"
display="block"
className="process__upload-btn"
placeholder="Username"
onChange={(e) => this.uploadImage(e, "multer")
}/>
<img
src={this.state.multerImage}
alt="upload-image"
className="process__image"/>
</div>
</div>
</div>
);
};
};
and finally this is where it's being rendered
// import {EditProfile} from './EditProfile'
import DisplayCats from '../cats/DisplayCats'
// import Button from '#material-ui/core/Button';
import Axios from 'axios';
import ImageUploader from '../ImageUploader';
function ProfilePage(props) {
console.log(props.userInfo)
const user = props.userInfo
console.log(user)
console.log(user._id)
console.log(props.userInfo._id)
let [responseData,
setResponseData] = useState('');
// getLocation = () => {
// navigator
// .geolocation
// .getCurrentPosition(function (position) {
// console.log(position)
// });
// }
const clickHandler = (e) => {
this
.props
.history
.push('/DisplayCats')
}
// const setProfileImage = (event) => {
// Axios
// .post('http://localhost:5000/api/users/updateImage/' + user._id, {
// "_id": user._id,
// "profileImage": event.target.value
// })
// .then(res => {
// setResponseData(res.data)
// console.log(responseData)
// }, function (err) {
// console.log(err)
// })
// }
return (
<div style={{
color: "black"
}}>
<h5>This is {props.userInfo.firstName}'s Profile Page</h5>
<h5>Last name: {props.userInfo.lastName}</h5>
<h5>Age: {props.userInfo.age}</h5>
<h5>Location:{props.userInfo.location}</h5>
<h5>Image:{props.userInfo.profileImage}</h5>
<h5>Biography:{props.userInfo.biography}</h5>
<ImageUploader user={user}/>
{/* <div className="col-md-6 img">
<img
src={responseData.profileImage}
alt="profile image"
className="img-rounded"/>
</div> */}
<div className="row">
<DisplayCats user={user}/>
</div>
{/*
<Button
variant="outlined"
color="primary"
onClick={this.clickHandler}
component={this.EditProfile}
user={this.props.user}>
Edit Info
</Button> */}
</div>
)
}
export default ProfilePage;
I want my image data and and image name to be created into a URL for my other functions to read it. My error comes back as POST /api/image/uploadmulter/ 404 97.290 ms - 163
Perhaps im missing something but from the code you shared, you set your route as:
ImageRouter.route("/upload")
so, your client side code should be posting to: http://localhost:5000/upload
yet, your code does this:
.post(`http://localhost:5000/api/image/uploadmulter/`
You're getting a 404 error, which makes sense since this route hasn't been defined.
On a related note, I'd recommend using something like react-uploady, to manage the uploads on your client-side. It will save you a lot of code and bugs. Especially if you want to show preview or other related functionality (like: progress, cancel, retry, etc.).

Avatar is saved to MongoDB but no success message and rendering of avatar in component, using React and Multer

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?

Uploading CSV file from react front end to node.js backend and storing it in the folder

I'm trying to upload a file to the backend folder using react's front end form input but I'm unable to do so.
I've added the form, I tried capturing data in the state but for some reason I can't store csv file in a state, is it because of file complexity?
Since I couldn't get the state to store my csv, I used axios to push data straight to the backend which seemed to work but the file was not saved in the backend.
I have 3 files, react App and node Upload
import React, {Component} from 'react';
import logo from './logo.svg';
import './App.css';
import CSVReader from 'react-csv-reader';
import axios from 'axios';
class App extends Component {
constructor(props) {
super(props);
this.state = {
apiResponse: '',
file: null,
};
this.handleImport = this.handleImport.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
callAPI() {
fetch("http://localhost:9000/testAPI")
.then(res => res.text())
.then(res => this.setState({ apiResponse: res }));
console.log(this.state.apiResponse)
}
componentDidMount() {
this.callAPI();
}
handleImport(data){
this.setState({file:data.target.files[0]})
//because I couldn't get state to work I used axios imediately with the data
axios.post("http://localhost:9000/upload", data, { // receive two parameter endpoint url ,form data
}).then(res => { // then print response status
console.log(res.statusText)
})
}
//I'm not using handlesubmit here as it involves a button press
handleSubmit(e){
e.preventDefault();
const data = new FormData();
data.append('file', this.state.file);
console.log(data);
axios.post("http://localhost:9000/upload", data, { // receive two parameter endpoint url ,form data
}).then(res => { // then print response status
console.log(res.statusText)
})
}
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p className='App-intro'> {this.state.apiResponse}</p>
</header>
<content className='body_cont'>
{/* My Form */}
<form action="POST">
<input type="file" name='file' onChange={this.handleImport} />
<button type='submit' onClick={this.handleSubmit}>Upload File</button>
</form>
</content>
</div>
);
}
}
export default App;
Next file is my upload file for which the routes have been set in app.js
var express = require('express');
var multer = require('multer');
var router = express.Router();
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, '../folder1/folder2/dm-portal/in')
},
filename: function (req, file, cb) {
cb(null, Date.now() + '-' +file.originalname )
}
});
var upload = multer({ storage: storage }).single('file')
router.post('/',function(req, res) {
console.log(req)
upload(req, res, function (err) {
if (err instanceof multer.MulterError) {
return res.status(500).json(err)
} else if (err) {
return res.status(500).json(err)
}
return res.status(200).send(req.file)
})
});
module.exports = router;
End results is I get a whole bunch of warning but the axios seemed to be working, however, the files is not being saved.
Any help appreciated.
P.S. I'm beginner.

Resources