How can I send image from client to server node js react - node.js

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

Related

How to receive Cloudinary image URL immediately upon upload (React JS and Node Js)?

I can successfully upload images to Cloudinary. But my question is how can I get the Cloudinary url of the successfully uploaded image sent back to me immediately upon upload?
I know it's sent back as part of const uploadedResponse = await cloudinary.uploader.upload(fileStr, {upload_preset: 'dev_setups'}), but this is on the backend (see code #2 below), I would like to receive the URL on the frontend (see code #1 below) so I can set it to React state. What is the best approach to accomplishing this?
Please let me know if you need more details.
Code #1: Below is my code to upload a picture to Cloudinary (Cloudinary specific code is commented below for reference as /* Cloudinary upload */)
import React, { useState } from 'react'
import { Card, Button, CardContent } from '#material-ui/core';
import { post, makePostAction } from '../actions';
import { useSelector, useDispatch } from 'react-redux';
export default function MakePost() {
const [title, setTitle] = useState("")
const dispatch = useDispatch();
const usernameHandle = useSelector(state => state.username)
const [fileInputState, setFileInputState] = useState('') /* new */
const [previewSource, setPreviewSource] = useState('') /* new */
const [selectedFile, setSelectedFile] = useState('') /* new */
const onInputChange = (event) => {
setTitle(event.target.value);
}
const handleSubmit = (evt) => {
evt.preventDefault();
if (!title) return
dispatch(makePostAction({
title,
comment: false,
comments_text: "",
handle: usernameHandle,
post_date: new Date().getTime()
}))
setTitle("")
}
/* Cloudinary upload */
const handleFileInputChange = (e) => {
const file = e.target.files[0]
previewFile(file)
}
const previewFile = (file) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onloadend = () => {
setPreviewSource(reader.result)
}
}
const handleSubmitFile = (e) => {
e.preventDefault();
if(!previewSource) return;
uploadImage(previewSource)
}
const uploadImage = async (base64EncodedImage) => {
console.log(base64EncodedImage)
try {
await fetch('/api/upload', {
method: 'POST',
body: JSON.stringify({data: base64EncodedImage}),
headers: {'Content-type': 'application/json'}
})
} catch (error) {
console.error(error)
}
}
/* Cloudinary upload */
return (
<div>
<Card>
<CardContent>
<form onSubmit={handleSubmit}>
<input type="text" value={title} onChange={onInputChange} />
</form>
{/* new */}
<form onSubmit={handleSubmitFile} className="form">
<input type="file" name="image" onChange={handleFileInputChange} value={fileInputState} className="form-input" />
<button className="btn" type="submit">Submit</button>
</form>
{/* new */}
{previewSource && (
<img
src={previewSource}
alt="chosen"
style={{height: '300px'}}
/>
)}
</CardContent>
</Card>
</div>
)
}
Code #2: Here is my server.js
const express = require('express');
const app = express();
const {cloudinary} = require('./utils/cloudinary');
app.use(express.json({limit: '50mb'}));
app.use(express.urlencoded({limit: '50mb', extended: true}))
app.get('/api/images', async (req, res) => {
const {resources} = await cloudinary.search.expression('folder:dev_setups')
.sort_by('public_id', 'desc')
.max_results(1)
.execute()
const publicIds = resources.map(file => file.secure_url)
res.send(publicIds)
})
app.post('/api/upload', async (req, res) => {
try {
const fileStr = req.body.data;
const uploadedResponse = await cloudinary.uploader.upload(fileStr, {upload_preset: 'dev_setups'})
res.json({msg: "Success"})
} catch (error){
console.error(error)
res.status(500).json({err: 'Something went wrong'})
}
})
const port = process.env.PORT || 3001
app.listen(port, () => {
console.log(`listening on port ${port}`)
});
The Cloudinary upload response object includes a secure_url attribute which you can send back to the front end. Looking at code #2, it seems that you're currently sending a "Success" msg (res.json({msg: "Success"})). Sounds like you want to change that line to -
res.json({url: uploadedResponse.secure_url})
In your front end (code #1), I'd consider switching from async/await to .then mechanism, as you don't want to the entire app to wait for the response -
const uploadImage = (base64EncodedImage) => {
console.log(base64EncodedImage);
fetch('/api/upload', {
method: 'POST',
body: JSON.stringify({data: base64EncodedImage}),
headers: {'Content-type': 'application/json'}
})
.then(doWhateverYouWant)
.catch((error) => console.error(error))
}
const doWhateverYouWant = async (res) => {
// you can use res.url
}

How can I send data from client to server in react and node.js? [duplicate]

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

React Hooks and Axios Image Upload with Multer

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" }})

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

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.

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