How to send a file/image from React to node.js server - node.js

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.

Related

req.file is showing undefined in console

I am trying to upload a video on youtube from my web app but I am getting an error in sending a file to my backend it is showing the req.file as undefined in req.body it is showing videoFile: "Undefined" and all other values I can see but I am not able to get the file from my react app to backend .
Input.jsx
import axios from "axios";
import React, { useState } from "react";
// import Axios from "axios";
function Input() {
const [form, setForm] = useState({
title: "",
description: "",
file: null,
});
function handleChange(event) {
const InputValue =
event.target.name === "file" ? event.target.file : event.target.value;
setForm({
...form,
[event.target.name]: InputValue,
});
}
function handleSubmit(event) {
event.preventDefault();
console.log({ form });
const headers = {
"content-type": "multipart/form-data",
};
const videoData = new FormData();
videoData.append("videoFile", form.file);
videoData.append("title", form.title);
videoData.append("description", form.description);
axios
.post("http://localhost:3001/upload", videoData, { headers })
.then((res) => {
console.log(res.data);
})
.catch((err) => console.log(err));
}
return (
<div className="form">
<h1>
<i className="fab fa-youtube"></i> Upload video
</h1>
<form onSubmit={handleSubmit} method="POST" enctype="multipart/form-data">
<div>
<input
className="title"
type="text"
placeholder="Title"
name="title"
autoComplete="off"
onChange={handleChange}
/>
</div>
<div>
<textarea
typeof="text"
placeholder="description"
name="description"
autoComplete="off"
onChange={handleChange}
/>
</div>
<div>
<input
type="file"
accept="video/mp4,video/x-m4v,video/*"
placeholder="Add"
name="file"
onChange={handleChange}
/>
</div>
<button className="btn btn-light" type="submit">
Upload Video
</button>
</form>
</div>
);
}
export default Input;
it is node.js part
App.js (backend)
const express = require("express");
const multer = require("multer");
const youtube = require("youtube-api");
const open = require("open");
const cors = require("cors");
const fs = require("fs");
const credentials = require("./client_secret_1013548512515-lti2tpl88m8qqkum1hh095cnevtdi3lu.apps.googleusercontent.com.json");
const app = express();
const bodyParser = require("body-parser");
app.use(express.json());
app.use(cors());
app.use(bodyParser.urlencoded({ extended: true }));
const storage = multer.diskStorage({
destination: "./",
filename: (req, file, cb) => {
const newFileName = `${Date.now()}-${file.originalname}`;
cb(null, newFileName);
},
});
const uploadVideoFile = multer({
storage: storage,
}).single("videoFile");
app.post("/upload", uploadVideoFile, (req, res) => {
console.log(req.body);
console.log(req.file); //it is showing undefined
if (req.file) { //getting error in this part
const filename = req.file.filename;
const { title, description } = req.body;
open(
oAuth.generateAuthUrl({
access_type: "offline",
scope: "https://www.googleapis.com/auth/youtube.upload",
state: JSON.stringify({
filename,
title,
description,
}),
})
);
}
});
app.get("/oauth2callback", (req, res) => {
res.redirect("http://localhost:3000/success");
const { filename, title, description } = JSON.parse(req.query.state);
oAuth.getToken(req.query.code, (err, tokens) => {
if (err) {
console.log(err);
return;
}
oAuth.setCredentials(tokens);
youtube.videos.insert(
{
resource: {
snippet: { title, description },
status: { privacyStatus: "private" },
},
part: "snippet,status",
media: {
body: fs.createReadStream(filename),
},
},
(err, data) => {
console.log("Done");
process.exit();
}
);
});
});
const oAuth = youtube.authenticate({
type: "oauth",
client_id: credentials.web.client_id,
client_secret: credentials.web.client_secret,
redirect_url: credentials.web.redirect_uris[0],
});
PORT = 3001;
app.listen(PORT, () => {
console.log("app is listening on port 3001");
});
Please make the following changes in the Input.jsx file.
// 1. Inside the handleChange() function
const InputValue = event.target.name === "file" ? event.target.files : event.target.value;
// 2. Inside the handleSubmit() function
videoData.append("videoFile", form.file[0], form.file[0].name);
This should output the file object inside req.file.

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

Uploading images from react to express

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 => {
})

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?

Upload image file from React front end to Node/Express/Mongoose/MongoDB back end (not working)

I’ve spent most of a day looking into this and trying to make it work. This is an app with a React/Redux front end, and a Node/Express/Mongoose/MongoDB back end.
I currently have a Topics system where an authorized user can follow/unfollow topics, and an admin can Add/Remove topics.
I want to be able to upload an image file when submitting a new topic, and I want to use Cloudinary to store the image and then save the images path to the DB with the topic name.
The problem I am having is that I am unable to receive the uploaded file on the back end from the front end. I end up receiving an empty object, despite tons of research and trial/error. I haven’t finished setting up Cloudinary file upload, but I need to receive the file on the back end before even worrying about that.
SERVER SIDE
index.js:
const express = require("express");
const http = require("http");
const bodyParser = require("body-parser");
const morgan = require("morgan");
const app = express();
const router = require("./router");
const mongoose = require("mongoose");
const cors = require("cors");
const fileUpload = require("express-fileupload");
const config = require("./config");
const multer = require("multer");
const cloudinary = require("cloudinary");
const cloudinaryStorage = require("multer-storage-cloudinary");
app.use(fileUpload());
//file storage setup
cloudinary.config({
cloud_name: "niksauce",
api_key: config.cloudinaryAPIKey,
api_secret: config.cloudinaryAPISecret
});
const storage = cloudinaryStorage({
cloudinary: cloudinary,
folder: "images",
allowedFormats: ["jpg", "png"],
transformation: [{ width: 500, height: 500, crop: "limit" }] //optional, from a demo
});
const parser = multer({ storage: storage });
//DB setup
mongoose.Promise = global.Promise;
mongoose.connect(
`mongodb://path/to/mlab`,
{ useNewUrlParser: true }
);
mongoose.connection
.once("open", () => console.log("Connected to MongoLab instance."))
.on("error", error => console.log("Error connecting to MongoLab:", error));
//App setup
app.use(morgan("combined"));
app.use(bodyParser.json({ type: "*/*" }));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());
router(app, parser);
//Server setup
const port = process.env.PORT || 3090;
const server = http.createServer(app);
server.listen(port);
console.log("server listening on port: ", port);
TopicController/CreateTopic
exports.createTopic = function(req, res, next) {
console.log("REQUEST: ", req.body); //{ name: 'Topic with Image', image: {} }
console.log("IMAGE FILE MAYBE? ", req.file); //undefined
console.log("IMAGE FILES MAYBE? ", req.files); //undefined
const topic = new Topic(req.body);
if (req.file) {
topic.image.url = req.file.url;
topic.image.id = req.file.publid_id;
} else {
console.log("NO FILE UPLOADED");
}
topic.save().then(result => {
res.status(201).send(topic);
});
};
router.js
module.exports = function(app, parser) {
//User
app.post("/signin", requireSignin, Authentication.signin);
app.post("/signup", Authentication.signup);
//Topic
app.get("/topics", Topic.fetchTopics);
app.post("/topics/newTopic", parser.single("image"), Topic.createTopic);
app.post("/topics/removeTopic", Topic.removeTopic);
app.post("/topics/followTopic", Topic.followTopic);
app.post("/topics/unfollowTopic", Topic.unfollowTopic);
};
CLIENT SIDE
Topics.js:
import React, { Component } from "react";
import { connect } from "react-redux";
import { Loader, Grid, Button, Icon, Form } from "semantic-ui-react";
import {
fetchTopics,
followTopic,
unfollowTopic,
createTopic,
removeTopic
} from "../actions";
import requireAuth from "./hoc/requireAuth";
import Background1 from "../assets/images/summer.jpg";
import Background2 from "../assets/images/winter.jpg";
const compare = (arr1, arr2) => {
let inBoth = [];
arr1.forEach(e1 =>
arr2.forEach(e2 => {
if (e1 === e2) {
inBoth.push(e1);
}
})
);
return inBoth;
};
class Topics extends Component {
constructor(props) {
super(props);
this.props.fetchTopics();
this.state = {
newTopic: "",
selectedFile: null,
error: ""
};
}
onFollowClick = topicId => {
const { id } = this.props.user;
this.props.followTopic(id, topicId);
};
onUnfollowClick = topicId => {
const { id } = this.props.user;
this.props.unfollowTopic(id, topicId);
};
handleSelectedFile = e => {
console.log(e.target.files[0]);
this.setState({
selectedFile: e.target.files[0]
});
};
createTopicSubmit = e => {
e.preventDefault();
const { newTopic, selectedFile } = this.state;
this.props.createTopic(newTopic.trim(), selectedFile);
this.setState({
newTopic: "",
selectedFile: null
});
};
removeTopicSubmit = topicId => {
this.props.removeTopic(topicId);
};
renderTopics = () => {
const { topics, user } = this.props;
const followedTopics =
topics &&
user &&
compare(topics.map(topic => topic._id), user.followedTopics);
console.log(topics);
return topics.map((topic, i) => {
return (
<Grid.Column className="topic-container" key={topic._id}>
<div
className="topic-image"
style={{
background:
i % 2 === 0 ? `url(${Background1})` : `url(${Background2})`,
backgroundRepeat: "no-repeat",
backgroundPosition: "center",
backgroundSize: "cover"
}}
/>
<p className="topic-name">{topic.name}</p>
<div className="topic-follow-btn">
{followedTopics.includes(topic._id) ? (
<Button
icon
color="olive"
onClick={() => this.onUnfollowClick(topic._id)}
>
Unfollow
<Icon color="red" name="heart" />
</Button>
) : (
<Button
icon
color="teal"
onClick={() => this.onFollowClick(topic._id)}
>
Follow
<Icon color="red" name="heart outline" />
</Button>
)}
{/* Should put a warning safety catch on initial click, as to not accidentally delete an important topic */}
{user.isAdmin ? (
<Button
icon
color="red"
onClick={() => this.removeTopicSubmit(topic._id)}
>
<Icon color="black" name="trash" />
</Button>
) : null}
</div>
</Grid.Column>
);
});
};
render() {
const { loading, user } = this.props;
if (loading) {
return (
<Loader active inline="centered">
Loading
</Loader>
);
}
return (
<div>
<h1>Topics</h1>
{user && user.isAdmin ? (
<div>
<h3>Create a New Topic</h3>
<Form
onSubmit={this.createTopicSubmit}
encType="multipart/form-data"
>
<Form.Field>
<input
value={this.state.newTopic}
onChange={e => this.setState({ newTopic: e.target.value })}
placeholder="Create New Topic"
/>
</Form.Field>
<Form.Field>
<label>Upload an Image</label>
<input
type="file"
name="image"
onChange={this.handleSelectedFile}
/>
</Form.Field>
<Button type="submit">Create Topic</Button>
</Form>
</div>
) : null}
<Grid centered>{this.renderTopics()}</Grid>
</div>
);
}
}
const mapStateToProps = state => {
const { loading, topics } = state.topics;
const { user } = state.auth;
return { loading, topics, user };
};
export default requireAuth(
connect(
mapStateToProps,
{ fetchTopics, followTopic, unfollowTopic, createTopic, removeTopic }
)(Topics)
);
TopicActions/createTopic:
export const createTopic = (topicName, imageFile) => {
console.log("IMAGE IN ACTIONS: ", imageFile); //this is still here
// const data = new FormData();
// data.append("image", imageFile);
// data.append("name", topicName);
const data = {
image: imageFile,
name: topicName
};
console.log("DATA TO SEND: ", data); //still shows image file
return dispatch => {
// const config = { headers: { "Content-Type": "multipart/form-data" } };
// ^ this fixes nothing, only makes the problem worse
axios.post(CREATE_NEW_TOPIC, data).then(res => {
dispatch({
type: CREATE_TOPIC,
payload: res.data
});
});
};
};
When I send it like this, I receive the following on the back end:
(these are server console.logs)
REQUEST: { image: {}, name: 'NEW TOPIC' }
IMAGE FILE MAYBE? undefined
IMAGE FILES MAYBE? undefined
NO FILE UPLOADED
If I go the new FormData() route, FormData is an empty object, and I get this server error:
POST http://localhost:3090/topics/newTopic net::ERR_EMPTY_RESPONSE
export const createTopic = (topicName, imageFile) => {
console.log("IMAGE IN ACTIONS: ", imageFile);
const data = new FormData();
data.append("image", imageFile);
data.append("name", topicName);
// const data = {
// image: imageFile,
// name: topicName
// };
console.log("DATA TO SEND: ", data); // shows FormData {} (empty object, nothing in it)
return dispatch => {
// const config = { headers: { "Content-Type": "multipart/form-data" } };
// ^ this fixes nothing, only makes the problem worse
axios.post(CREATE_NEW_TOPIC, data).then(res => {
dispatch({
type: CREATE_TOPIC,
payload: res.data
});
});
};
};
Solution was to switch to using Firebase instead, and deal with image upload on the React client (this was attempted with cloudinary but with no success). The resulting download url can be saved to the database with the topic name (which is all I wanted from cloudinary) and now it is displaying the correct images along with the topics.

Resources