How to create a comment and reply section with MERN STACK - node.js

I created a comment session on my entertainment website
It’s working on backend.
It’s working on the frontend also but it’s not displaying the content the user typed on the database
This is my frontend (Comment form) logic:
export default function AddComment({ busy}) {
const [content, setContent] = useState("");
const { movieId } = useParams();
const { updateNotification } = useNotification();
const handleOnChange = ({ target }) => {
setContent(target.value);
};
const handleSubmit = async (e) => {
e.preventDefault();
const { error, message } = await addComment(movieId);
if (error) return updateNotification("error", error);
updateNotification("success", message);
const newComment = {
content,
};
setContent(newComment);
setContent("");
};
return (
<div className='p-5'>
<br />
<p className='dark:text-white text-primary'>replies</p>
<hr className='w-64' />
{/* Comment Lists */}
{/* Root Comment Form */}
{/* Form */}
<form className='flex ' onSubmit={handleSubmit} busy={busy}>
<textarea
value={content}
onChange={handleOnChange}
type='text'
autoComplete='text'
className='w-full rounded-md p-2 dark:text-white text-primary outline-none bg-transparent resize-none border-b focus:border-blue-500'
placeholder='Add New comment'
/>
<br className='dark:text-white text-primary ' />
<button
type='submit'
className=' w-5 h-14 dark:text-white text-primary bg-blue-600 hover:bg-blue-400 focus:border-blue-900 rounded-md'
>
{busy ? <ImSpinner3 className='animate-spin' /> : "Add"}
</button>
</form>
</div>
);
}
Then the addComment is coming from this API:
import { catchError, getToken } from "../utils/helper";
import client from "./client";
export const addComment = async (movieId, newComment) => {
const token = getToken();
try {
const { data } = await client.post(
`/comments/comment/${movieId}`,
newComment,
{
headers: {
authorization: "Bearer " + token,
},
}
);
return data;
} catch (error) {
return catchError(error);
}
};
The backend is working:
exports.createComment = expressAsyncHandler(async (req, res) => {
const { movieId } = req.params;
const { content } = req.body;
const userId = req.user._id;
console.log(req.body);
// verify user before comment
if (!req.user.isVerified)
return sendError(res, "Please verify your email first!");
if (!isValidObjectId(movieId)) return sendError(res, "Invalid Movie!");
// create and update new comment
const newComment = new Comment({
user: userId,
parentMovie: movieId,
content,
});
// save new comment
await newComment.save();
res.json({ message: "New comment added!!", newComment });
});
I posted with Postman on backend it gave me this on the database:
_id
:
62dcfccd93444cef55611632
user
:
62bf20d65073a7c65f549078
parentMovie
:
62c2c425465804ff32cdd06c
content
:
"hello"
createdAt
:
2022-07-24T08:03:25.666+00:00
updatedAt
:
2022-07-24T08:03:25.666+00:00
__v
:
0
on the console:
The port is listening on port 8000
connected to db
{ content: 'hello' }
POST /api/comments/comment/62c2c425465804ff32cdd06c 200 447.534 ms - 260
I posted on the frontend it gave me this on the database, no content:
_id
:
62dcfd6993444cef55611635
user
:
62bf57e8a8f3e737b2af23d9
parentMovie
:
62cc1d426785cfe42f8737a8
createdAt
:
2022-07-24T08:06:01.458+00:00
updatedAt
:
2022-07-24T08:06:01.458+00:00
__v
:
0
on the console it shows an empty object:
{}
POST /api/comments/comment/62cc1d426785cfe42f8737a8 200 364.009 ms - 242

This is how I solved the problem
Hope this solution will help many
const handleSubmit = async (e) => {
e.preventDefault();
const { error, message } = await addComment(movieId, content); // call the content and movieId from backend
if (error) return updateNotification("error", error);
updateNotification("success", message);
// push and display the content on database
const newComment = {
content,
};
setContent(newComment);
setContent("");
};
Then the API should be like this
export const addComment = async (movieId, newComment) => {
const token = getToken();
// console.log(newComment);
const body = {
content: newComment,
};
try {
const { data } = await client.post(`/comments/comment/${movieId}`, body, {
headers: {
authorization: "Bearer " + token,
},
});
return data;
} catch (error) {
return catchError(error);
}
};

Related

React+Node webpage - Update endpoint throws 404 error

I have setup a webpage to search a number via user input and if it's available in the SQL Server database, 2 text boxes would show up with the data using AXIOS GET endpoint. Then I'm trying to get those ID's of the data rows and if the user needs to UPDATE it, then UPDATE it via AXIOS PUT endpoint. The issue is once user clicks the UPDATE button it throws an error PUT http://localhost:5000/api/customerOrder/[object%20Object],[object%20Object] 404 (Not Found)
Here's what I've tried
Server endpoints :
dboperations.js
var config = require('./dbconfig');
const sql = require('mssql');
async function getallcustomerOrders(){
try{
let pool = await sql.connect(config);
let orders = await pool.request()
.query("SELECT * FROM [100].[dbo].[OELINCMT_SQL] order by ID desc");
return orders.recordsets;
}
catch (error){
console.log(error);
}
}
async function getcustomerOrders(orderNumber){
try{
let pool = await sql.connect(config);
let orders = await pool.request()
.input('input_parameter', sql.NChar, orderNumber)
.query("SELECT ID,cmt FROM [100].[dbo].[OELINCMT_SQL] where LTRIM(ord_no) = LTRIM(#input_parameter)");
return orders.recordsets;
}
catch (error){
console.log(error);
}
}
async function updateComments(ID){
try {
let pool = await sql.connect(config);
let orders = await pool.request()
.input('ID', sql.NChar, ID)
.query(`SELECT ID,cmt FROM [100].[dbo].[OELINCMT_SQL] WHERE ID = #ID`);
let order = orders.recordset.length ? orders.recordset[0] : null;
if (order) {
await pool.request()
.input('cmt', req.body.cmt)
.query(`UPDATE [100].[dbo].[OELINCMT_SQL] SET cmt = #cmt WHERE ID = #ID;`);
order = { ...order, ...req.body };
res.json(order);
} else {
res.status(404).json({
message: 'Record not found'
});
}
} catch (error) {
res.status(500).json(error);
}
}
module.exports = {
getallcustomerOrders : getallcustomerOrders,
getcustomerOrders : getcustomerOrders,
updateComments : updateComments
}
api.js
var Db = require('./dboperations');
var dboperations = require('./dboperations');
var express = require('express');
var bodyParser = require('body-parser');
var cors = require('cors');
const { request, response } = require('express');
var app = express();
var router = express.Router();
app.use(bodyParser.urlencoded({ extended: true}));
app.use(bodyParser.json());
app.use(cors());
app.use('/api', router);
router.use((request,response,next)=> {
console.log('middleware');
next();
})
router.route('/customerOrder').get((request,response)=>{
dboperations.getallcustomerOrders().then(result => {
response.json(result[0]);
console.log(result[0]);
})
})
router.route('/customerOrder/:orderNumber').get((request,response)=>{
dboperations.getcustomerOrders(request.params.orderNumber).then(result => {
response.json(result[0]);
console.log(result[0]);
})
})
router.route('customerOrder/:ID').put((request,response)=>{
dboperations.updateComments(request.params.ID).then(result => {
response.json(result[0]);
console.log(result[0]);
})
})
var port = process.env.PORT || 5000;
app.listen(port);
console.log('Customer Order API is running at ' + port);
dboperations.getcustomerOrders().then(result => {
console.log(result);
})
dboperations.getallcustomerOrders().then(result => {
console.log(result);
})
dboperations.updateComments().then(result => {
console.log(result);
})
Client :
EmailFaxDetails.js : This is the page user enters the number
import React, { useState,useEffect } from 'react'
import FetchOrderDetails from './FetchOrderDetails';
import axios from 'axios'
import '../App.css';
const EmailFaxDetails = () => {
const [orderNumber, setOrderNumber] = useState('');
const [id, setId] = useState([]);
const [isShown, setIsShown] = useState(false);
const url = `http://localhost:5000/api/customerOrder/${orderNumber}`
useEffect(() => {
axios.get(url)
.then(response => {
console.log(response.data)
setId(response.data)
})
.catch((err) => console.log(err));
}, [url]);
const handleChange = event => {
setOrderNumber(event.target.value);
console.log(event.target.value);
};
const handleClick = event => {
event.preventDefault();
setIsShown(true);
console.log(orderNumber);
}
return(
<div>
<br></br>
<br></br>
Order Number: <input placeholder="Order Number" type="text" id="message" name="message" onChange={handleChange} value={orderNumber} autoComplete="off" />
{id.map((idnum) => (
<div key={idnum.ID}>
<br></br>
ID : {idnum.ID}
</div>
))}
<button onClick={handleClick}>Search</button>
{isShown && <FetchOrderDetails ord_no={orderNumber} ID={id}/>}
</div>
)
}
export default EmailFaxDetails;
FetchOrderDetails.js : In this page user get's the output if the number is available in SQL server and let then UPDATE accordingly.
import React, { useEffect, useState } from 'react'
import axios from 'axios'
import '../App.css';
const FetchOrderDetails = ({ord_no,ID}) => {
const [data, setData] = useState([]);
const url = `http://localhost:5000/api/customerOrder/${ord_no}`
useEffect(() => {
axios.get(url)
.then(response => {
console.log(response.data)
setData(response.data)
})
.catch((err) => console.log(err));
}, [url]);
const url2 = `http://localhost:5000/api/customerOrder/${ID}`
const onSubmit = () => {
axios.put(url2)
.then((response) => {
if (response.status === 200) {
alert("Comment successfully updated");
ID.history.push(`/customerOrder/${ord_no}`);
} else Promise.reject();
})
.catch((err) => alert("Something went wrong"));
}
if(data) {
return(
<div>
{data.map((order) => (
<div key={order.ID}>
<br></br>
ID : {order.ID}
<br></br>
Email/Fax: <input defaultValue={order.cmt} placeholder="Sales Ack Email" id="salesAck" style={{width: "370px"}} />
</div>
))}
<div>
<br></br>
<br></br>
<button onClick={onSubmit}>Update</button>
</div>
</div>
)
}
return (
<h1>Something went wrong, please contact IT!</h1>
)
}
export default FetchOrderDetails;
What I suspect is the issue might be coming from the EmailFaxDetails.js page while trying to pass the ID since there are 2 ID's per number the user search. I might be wrong, if anyone could find the error and help making it correct I would really appreciate it.
I think problem here
setId(response.data)
You need retrieve only id and same for for orderid

React use axios return Axios Error {message: 'Network Error'} and TypeError: Cannot read properties of undefined (reading 'path')

when I click submit button, then web and terminal will return error like me title
but i try postman is ok , so i think is my axios setting error,how can i fixed this error? I found many similar questions, but can't not help me
the other question is , my form tag action is "/addItems", but i sending request , i got this error CANNOT POST / addItems Post http://localhost:3000/addItems 404 (Not Found)
(axios setting )
post(id, title, description, price, avatar) {
let token;
if (localStorage.getItem("user")) {
token = JSON.parse(localStorage.getItem("user")).token;
} else {
token = "";
}
const formData = new FormData();
// formData.append("id", id);
// formData.append("title", title);
// formData.append("description", description);
// formData.append("price", price);
formData.append("avatar", avatar);
return axios.post(
API_URL + "/addItems",
{ formData },
{
headers: {
Authorization: token,
"Content-Type": "multipart/form-data"
}
}
);
}
(item.route)
itemRouter.post("/addItems", upload.single("avatar"), async (req, res) => {
let { id, title, description, price, avatar } = req.body;
if (req.user.isMember()) {
return res.status(400).send("Only admin can add new items");
}
console.log(req.file);
avatar = req.file.path;
const newItem = new Item({
id,
title,
description,
price,
avatar
});
try {
await newItem.save();
console.log(req.file);
res.status(200).send("New item has been saved.");
} catch (err) {
res.status(400).send("Error");
console.log(err);
}
});
(addItemsComponent)
const handleChangePost = () => {
if (currentUser.user.role !== "admin") {
window.alert("Member can't not post item!! ");
navigate("/");
} else {
ItemService.post(avatar)
.then(() => {
window.alert("Post successfully");
navigate("/");
})
.catch((error) => {
console.log(error);
console.log(error.response);
setErrorMessage(error.response.data);
});
}
};
return (
<div>
<form action="/addItems" method="post" enctype="multipart/form-data">
<input onChange={handleChangeAvatar} value={avatar} type="file" name="avatar" />
<button type="submit" onClick={handleChangePost}>
Submit
</button>
</form>
</div>
);
Try this
return axios.post(API_URL + "/addItems", formData,
{
headers: {
Authorization: token,
"Content-Type": "multipart/form-data"
}
}
);

update real time in react and nodejs [duplicate]

This question already exists:
update context real time in react [duplicate]
Closed 1 year ago.
I am trying to achieve when user upload their profile image on website and it auto change the old profile picure into new profile picture instead user have to log out and log back in to make it works.
Here is my front end code base:
const UserCard = ({ picture, name, userEmail, isVerified, id, setPicture, setUser}) => {
const [imageSelected, setImageSelected] = useState("");
useEffect(() => {
if (imageSelected !== '') {
uploadImage();
}
}, [imageSelected]);
const uploadImage = () => {
const formData = new FormData();
formData.append("file", imageSelected);
formData.append("id", id);
axios
.post("/api/v1/users/upload/image", formData, {
headers: { "Content-Type": "multipart/form-data" },
})
.then((response) => {
setPicture(response.data.data.imageUrl);
setUser(prev => ({ ...prev, picture: response.data.data.imageUrl }));
});
};
// End of Method
const inputFile = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted file input element
inputFile.current.click();
};
return (
<div className="avatar--icon_profile">
<Card className="profile--card_container">
<CardContent>
{picture ? (
<div>
<input
className="my_file"
type="file"
ref={inputFile}
onChange={(e) => setImageSelected(e.target.files[0])}
/>
<div className="profile-image">
<Avatar
src={picture}
alt="Avatar"
className="avatar--profile_image"
onClick={onButtonClick}
/>
</div>
</div>
</div>
and here is my backend router to send image from client to cloudinary (where I store all the images):
router.post('/upload/image', function (req, res, next) {
const dUri = new Datauri();
const dataUri = (req) => dUri.format(path.extname(req.name).toString(), req.data);
if (req.files !== undefined && req.files !== null) {
const { file, id } = req.files;
const newFile = dataUri(file).content;
cloudinary.uploader.upload(newFile)
.then(result => {
const imageUrl = result.url;
const data = {id : req.body.id, imageUrl };
updateAvatar(data);
return res.status(200).json({ message: 'Success', data: { imageUrl } });
}).catch(err => res.status(400).json({message:'Error', data: { err}}));
} else {
return res.status(400).json({ message: 'Error' });
}
});
How can I achieve it ?
Added GlobalState.js:
const GlobalState = (props) => {
// User State -----------------------------------------------------------------------------
const [currentUser, setUser] = useState(props.serverUserData);
// This method is passed through context to update currentUser
const updateUser = (userData) => {
setUser(userData);
};
// Modal State -----------------------------------------------------------------------------
const [isModalOpen, setModalOpenState] = useState(false);
const [modalToDisplay, setModalToDisplay] = useState("signup");
// This function will be provided to any function that needs to toggle the modal.
const toggleModal = () => {
// Take the previous state and flip it.
setModalOpenState((prevState) => !prevState);
};
// This method is passed through context to update the next modal to open.
const setModal = (name) => {
// Take the passed in modal name and set state.
setModalToDisplay(name);
};
// Loading State ---------------------------------------------------------------------------
// NOT REACT STATE
const [loading, setLoadingState] = useState(false);
// This state will be used as messages in effects to refetch data.
const [reloadThisData, setWhatToReload] = useState("");
// User profile id for query ----------------------------------------------------------------
const [userProfileId, setUserProfileId] = useState("");
// Flag to determine if the header should change css style. ----------------------------------------------------------------
const [adjustBrightness, setAdjustBrightness] = useState(false);
// This is the object passed to GlobalContext.Provider
const providerValues = {
isModalOpen,
toggleModal,
modalToDisplay,
setModal,
currentUser,
updateUser,
loading,
setLoadingState,
reloadThisData,
setWhatToReload,
userProfileId,
setUserProfileId,
adjustBrightness,
setAdjustBrightness,
};
return (
<GlobalContext.Provider value={providerValues}>
{props.children}
</GlobalContext.Provider>
);
};
export default GlobalState;
Added console.log(currentUser):
{id: "a9aa869e-e28b-4a06-b5c7-88571d490e04", name: "nhan nguyen", email: "nhannguyen4119#gmail.com", publicId: "nh1615539696370", picture: "http://res.cloudinary.com/teammateme/image/upload/v1617073225/hvckrm6bklbpjk9njrlf.jpg", …}
email: "nhannguyen4119#gmail.com"
id: "a9aa869e-e28b-4a06-b5c7-88571d490e04"
isSessionValid: true
name: "nhan nguyen"
picture: "http://res.cloudinary.com/teammateme/image/upload/v1617073225/hvckrm6bklbpjk9njrlf.jpg"
publicId: "nh1615539696370"
__proto__: Object
Update my code after edit:
const UserProfile = () => {
const appState = useContext(GlobalContext);
const { currentUser, setUser } = appState;
const { email, name, id } = currentUser;
const [isVerified, setIsVerified] = useState(false);
const [picture, setPicture] = useState(currentUser.picture);
const checkVerificationData = () => {
axios.get("/api/v1/profiles/profile").then((res) => {
const { data } = res;
if (data.verifiedDT) {
setIsVerified(data.verifiedDT);
}
});
};
useEffect(() => {
checkVerificationData();
}, [isVerified]);
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={picture}
setPicture={setPicture}
userEmail={email}
name={name}
isVerified={isVerified}
id={id}
setUser={setUser}
/>
<br />
</Grid>
<div>
<Grid item>
<div className="profile--layout_userInfo">
<Grid item>
<UserInfo />
</Grid>
</div>
</Grid>
</div>
</Grid>
<div>
<Grid item>
<div className="profile--layout_ratings_reviews_block">
<UserRatingsDetailed userEmail={email} />
</div>
</Grid>
</div>
</Grid>
</div>
);
};
export async function getServerSideProps(context) {
let serverUserData = {};
if (
context.req.session.passport !== undefined &&
context.req.session.passport.user !== undefined
) {
const userPassportInfo = context.req.session.passport.user;
const { id, name, email, publicId, picture } = userPassportInfo;
const isSessionValid = context.req.isAuthenticated();
serverUserData = {
id,
name,
email,
publicId,
picture,
isSessionValid,
};
}
return { props: { serverUserData } };
}
UserProfile.propTypes = {
serverUserData: PropTypes.object,
};
export default UserProfile;
My suggestion from the comments in answer form is to create a manager similar to your other managers in the global context of your react application.
const GlobalState = (props) => {
// Profile Image -----------------------------------------------------------------------
const [currentProfileImage, setProfileImage] = useState(/*props.serverUserData.profileImage??*/);
...
};
export default GlobalState;
Don't forget to update your providerValues to include these new values.
Then, anywhere you use the profile image URI use the currentProfileImage variable from your context provider.
Lastly, when you upload your image to your server, and receive the new URI in the response use the setProfileImage function from global state to update the profile image in your global state.
const uploadImage = () => {
const formData = new FormData();
formData.append("file", imageSelected);
formData.append("id", id);
axios.post("/api/v1/users/upload/image", formData, {
headers: {"Content-Type": "multipart/form-data"},
}).then((response) => {
GlobalState.setProfileImage(response.data.data.imageUrl);
});
};
*I don't have a complete view of your program, and so this is my best guess of a reasonable way to implement your desired behaviour.
You'll need to update the initial state of the useState directive to reflect where in your props structure the profileImage URI is actually located, and you'll need to update the GlobalState placeholder to reflect however you are actually accessing your provided context.

_id is missing after doing actions

i'm currently creating my first MERN App, and everything is going well, until something happened, and i'm going my try to explain because i need help !
What i'm doing is a facebook clone, where you can post something, you can delete your post and you can update your post, the logic is simple, i call dispatch to pass the data to the actions, the actions pass the data to the backend, and the backend return something to me and it saves in my store, because i'm using redux
The problem is that, when i have 2 post, and i want to delete a post, or maybe i want to edit it, the other post dissapears, it's like it loses its id and then loses the information, then i can't do anything but reaload the page, and it happens always
this is how it looks like, everything fine
Then, after trying to edit a post, the second one lost its information, and in the console, it says that Warning: Each child in a list should have a unique "key" prop, and i already gave each post the key={_id}, but the post lost it and i don't know how
Here's the code
Posts.js
import React, { useState } from "react";
import "./Posts.css";
import moment from "moment";
// Icons
import { BiDotsVertical, BiLike } from "react-icons/bi";
import { MdDeleteSweep } from "react-icons/md";
import { AiFillLike } from "react-icons/ai";
import { GrClose } from "react-icons/gr";
// Calling actions
import { deletePost, } from "../actions/posts.js";
// Gettin The Data From Redux
import { useSelector, useDispatch } from "react-redux";
const Posts = ({ setCurrentId }) => {
const [animation, setAnimation] = useState(false);
const [modal, setModal] = useState(false);
const [modalPost, setModalPost] = useState({});
// Getting The Posts
const posts = useSelector(state => state.posts);
const dispatch = useDispatch();
// Showing And Hiding Modal Window
const ModalWindow = post => {
setModalPost(post);
setModal(true);
};
// Liking the post
// const Like = id => {
// dispatch(giveLike(id));
// setAnimation(!animation);
// };
if (!posts.length) {
return <div>Loading</div>;
} else {
return (
<div className="Posts">
{/* // Modal window for better look to the post */}
{/* {modal && (
<div className="modalWindow">
<div className="container">
<div className="container-image">
<img src={modalPost.image} alt="" />
</div>
<div className="information">
<div className="container-information">
<div className="data-header">
<h2>
User <br />{" "}
<span style={{ fontWeight: "400" }}>
{moment(modalPost.createdAt).fromNow()}
</span>
</h2>
<span className="data-icon" onClick={() => setModal(false)}>
<GrClose />
</span>
</div>
<div className="message">
<h2>{modalPost.title}</h2>
<p>{modalPost.message}</p>
</div>
</div>
</div>
</div>
</div>
)} */}
{/* */}
{posts.map(post => {
const { _id, title, message, image, createdAt, likes } = post;
return (
<div className="Posts-container" key={_id}>
<div className="Fit">
<div className="Fit-stuff">
<h2 className="Fit-stuff_title">
User <br />{" "}
<span style={{ fontWeight: "400" }}>
{moment(createdAt).fromNow()}
</span>
</h2>
<a
className="Fit-stuff_edit"
href="#form"
onClick={() => setCurrentId(_id)}
>
<BiDotsVertical />
</a>
</div>
<div className="Fit-data">
<h2 className="Fit-data_title">{title}</h2>
<p className="Fit-data_message">{message}</p>
{image ? (
<div className="Fit-img">
<img
onClick={() => ModalWindow(post)}
src={image}
alt=""
/>
</div>
) : (
<div></div>
)}
</div>
<div className="Fit-shit">
<span>
{animation ? (
<AiFillLike className="fullLightBlue" />
) : (
<BiLike />
)}
{likes}
</span>
<span onClick={() => dispatch(deletePost(_id))}>
<MdDeleteSweep />
</span>
</div>
</div>
</div>
);
})}
</div>
);
}
};
export default Posts;
The form where i call update and create Post
import React, { useState, useEffect } from "react";
import Filebase from "react-file-base64";
// For the actions
import { useDispatch, useSelector } from "react-redux";
import { createPost, updatePost } from "../actions/posts.js";
import {
Wrapper,
FormContainer,
Data,
DataInput,
SecondDataInput,
FormContainerImg,
FormContainerButtons,
Buttons
} from "./FormStyled.js";
const Form = ({ currentId, setCurrentId }) => {
const [formData, setFormData] = useState({
title: "",
message: "",
image: ""
});
const specificPost = useSelector(state =>
currentId ? state.posts.find(p => p._id === currentId) : null
);
// Sending The Data And Editing The data
const dispatch = useDispatch();
useEffect(() => {
if (specificPost) setFormData(specificPost);
}, [specificPost]);
// Clear Inputs
const clear = () => {
setCurrentId(0);
setFormData({ title: "", message: "", image: "" });
};
const handleSubmit = async e => {
e.preventDefault();
if (currentId === 0) {
dispatch(createPost(formData));
clear();
} else {
dispatch(updatePost(currentId, formData));
clear();
}
};
return (
<Wrapper>
<FormContainer onSubmit={handleSubmit}>
<Data>
<DataInput
name="title"
maxLength="50"
placeholder="Title"
type="text"
value={formData.title}
onChange={e => setFormData({ ...formData, title: e.target.value })}
/>
<SecondDataInput
name="message"
placeholder="Message"
maxLength="300"
value={formData.message}
required
onChange={e =>
setFormData({ ...formData, message: e.target.value })
}
/>
<FormContainerImg>
<Filebase
required
type="file"
multiple={false}
onDone={({ base64 }) =>
setFormData({ ...formData, image: base64 })
}
/>
</FormContainerImg>
<FormContainerButtons>
<Buttons type="submit" create>
{specificPost ? "Edit" : "Create"}
</Buttons>
<Buttons onClick={clear} clear>
Clear
</Buttons>
</FormContainerButtons>
</Data>
</FormContainer>
</Wrapper>
);
};
export default Form;
My actions
import {
GETPOSTS,
CREATEPOST,
DELETEPOST,
UPDATEPOST,
LIKEPOST
} from "../actionTypes/posts.js";
import * as api from "../api/posts.js";
export const getPosts = () => async dispatch => {
try {
const { data } = await api.getPosts();
dispatch({ type: GETPOSTS, payload: data });
} catch (error) {
console.log(error);
}
};
export const createPost = newPost => async dispatch => {
try {
const { data } = await api.createPost(newPost);
dispatch({ type: CREATEPOST, payload: data });
} catch (error) {
console.log(error);
}
};
export const updatePost = (id, updatePost) => async dispatch => {
try {
const { data } = await api.updatePost(id, updatePost);
dispatch({ type: UPDATEPOST, payload: data });
} catch (error) {
console.log(error);
}
};
export const deletePost = id => async dispatch => {
try {
await api.deletePost(id);
dispatch({ type: DELETEPOST, payload: id });
} catch (error) {
console.log(error);
}
};
Redux Part
import {
GETPOSTS,
CREATEPOST,
DELETEPOST,
UPDATEPOST,
LIKEPOST
} from "../actionTypes/posts.js";
const postData = (posts = [], action) => {
switch (action.type) {
case GETPOSTS:
return action.payload;
case CREATEPOST:
return [...posts, action.payload];
case UPDATEPOST:
return posts.map(post =>
action.payload._id === post._id ? action.payload : posts
);
case DELETEPOST:
return posts.filter(post => post._id !== action.payload);
default:
return posts;
}
};
export default postData;
My controllers in the backend
import mongoose from "mongoose";
import infoPost from "../models/posts.js";
// Getting All The Posts
export const getPosts = async (req, res) => {
try {
const Posts = await infoPost.find();
res.status(200).json(Posts);
} catch (error) {
res.status(404).json({ message: error.message });
console.log(error);
}
};
// Creating A Post
export const createPost = async (req, res) => {
const { title, message, image } = req.body;
const newPost = new infoPost({ title, message, image });
try {
await newPost.save();
res.status(201).json(newPost);
} catch (error) {
res.status(409).json({ message: error.message });
console.log(error);
}
};
// Update A Post
export const updatePost = async (req, res) => {
const { id } = req.params;
const { title, message, image } = req.body;
if (!mongoose.Types.ObjectId.isValid(id))
return res.status(404).send(`No Post With Id Of ${id}`);
const updatedPost = { title, message, image, _id: id };
await infoPost.findByIdAndUpdate(id, updatedPost, { new: true });
res.json(updatedPost);
};
// Deleting A Post
export const deletePost = async (req, res) => {
const { id } = req.params;
if (!mongoose.Types.ObjectId.isValid(id))
return res
.status(404)
.send(`We Couldnt Found The Post With Id Of ${id} To Delete`);
await infoPost.findByIdAndRemove(id);
res.json(`Post With Id Of ${id} Deleted Succesfully`);
};
// Liking A Post
export const likePost = async (req, res) => {
const { id } = req.params;
if (!mongoose.Types.ObjectId.isValid(id))
return res.status(404).send(`No post with id: ${id}`);
const post = await infoPost.findById(id);
const updatedPost = await infoPost.findByIdAndUpdate(
id,
{ likeCount: post.likeCount + 1 },
{ new: true }
);
res.json(updatedPost);
};
Even though i've been trying to solve this problem for nearly 3.5 hours, i think that the problem might be in my Posts.js part, if you can help me, you're the greatest !

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