mern - updated values are null in data - node.js

I'm trying to update the posts. The PUT request in the back end works fine, returning 200 and updates posts when tested on Postman however when I try to update a post in the front end (react), I'm not receiving any errors but the updated post isn't being updated on submit and the updated fields (title and body) are null. The updated values are null when I console.log(data) in the front end which is why they aren't being sent to the back end but they are shown correctly in post.
Why are the updated values null inside data? How can I update the post with the new values instead of getting null?
data:
post:
Updated code: Frontend
const EditPost = ({match}) => {
const [values, setValues] = useState({
title: "",
body: "",
error: ""
});
const [post, setPost] = useState({});
const { user, token } = isAuthenticated();
const {
title,
body,
error,
} = values;
const init = (id) => {
read(id).then(data => {
if (data.error) {
setValues({...values, error: data.error})
} else {
setValues({...values,
title: data.title,
body: data.body,
})
setPost({title: values.title, body: values.body})
}
})
}
useEffect(() => {
const id = match.params.id;
init(id);
}, []);
useEffect(() => {
setPost({...values });
}, [values.title, values.body]);
const handleChange = (name) => (event) => {
setValues({ ...values, [name]: event.target.value });
};
const clickSubmit = (event) => {
event.preventDefault();
setValues({ ...values, error: "" });
editPost(match.params.userId, match.params.id, token, post).then((data) => {
if (data.error) {
setValues({ ...values, error: data.error });
} else {
setValues({
...values,
title: "",
body: "",
error: false,
});
console.log(post)
console.log(data)
}
});
};
const newPostForm = () => (
<form onSubmit={clickSubmit}>
<div>
<input
onChange={handleChange("title")} type="text"
name="title"
value={title}
/>
</div>
<div className="form-group">
<textarea
onChange={handleChange("body")}
value={body} name="body"
/>
</div>
<button type="submit">Publish</button>
</form>
);
const showError = () => (
<div
style={{ display: error ? "" : "none" }}>
{error}
</div>
);
return (
<div>
{showError()}
{newPostForm()}
</div>
);
};
export default EditPost;
export const editPost = (userId, id, token, post) => {
return fetch(`${API}/${userId}/${id}/edit`, {
method: 'PUT',
headers: {
Accept: 'application/json',
Authorization: `Bearer ${token}`
},
body: JSON.stringify(post)
})
.then(response => {
return response.json();
})
.catch(err => console.log(err));
};
postsByUser.js
<Link className="mypost_btn edit_btn" to={`/${_id}/${post._id}/edit`}>
Edit
</Link>
Backend code
exports.edit = (req, res) => {
if (!ObjectID.isValid(req.params.id))
return res.status(400).send(`ID is not valid: ${req.params.id}`)
const {title, body} = req.body
const updatedPost = {title, body }
Post.findByIdAndUpdate(req.params.id, {
$set: updatedPost
}, {new:true}, (error, data) => {
if (error) {
return error
} else {
res.send(data)
console.log(data)
}
})
}

Your problem lies here:
editPost(match.params.userId, match.params.id, token, post)
post is not defined.
Since post is not defined, no data is passed. Hence title and body equal to null. What you will need to do is, assuming from what I'm seeing on your code, is to define a state variable called post. I think you intended to do that:
const [post, setPost] = useState({values.title, values.body});
Then ensure that your post is updated whenever your values change using useEffect(),
useEffect(() => {
setPost({...values });
}, [values.title, value.body]);
So by the time you call your editPost() http-put-method, then post has a value. And it should work.

in EditPost.js editPost(match.params.userId, match.params.id, token).then((data) => { here you are missing the 4th arg which is the "post" it self you send to be updated

Related

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

Can't print data from database in react. Getting TypeError: Cannot read properties of undefined (reading 'productname')

router.get("/product",async(req,res) => {
try {
const data = await Product.find({});
console.log(data);
res.send(data);
} catch (error) {
console.log(error);
}
});
The code above fetches the data from database successfully and also prints in the console.
Now in frontend
function Product() {
useEffect (() => {
fetchItems();
},[]);
const [items, setItems] = useState([]);
const fetchItems = async() => {
const data = await fetch('/product',{
method: "GET",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
credentials: "include"
});
const items = await data.json();
setItems(items);
};
return (
<div>
<h1>Product Page</h1>
{
items.map(item => {
<p>{item.product.productname} {item.product.producttype} {item.product.productprice}</p>
})
}
</div>
)
}
I can't print any items as I am getting a type error. The name of the collection is products.
It's because your items render first and your state set after return it's common issue in react
just do:
return (
<div>
<h1>Product Page</h1>
{(items?.length > 0) &&
items?.map(item => {
<p>{item?.product?.productname} {item?.product?.producttype} {item?.product?.productprice}</p>
})
}
</div>
)

how to update context state in react

I am having a problem that when user upload their profile image it did not change, user have to log out and log back in to make a change complete.
Here is my back end how to get image from client and store it on cloudinary:
profilesController.js:
exports.updateAvatar = async (req, res) => {
// Find user with matching token
// const updates = [];
const updateUserAvatar = await models.User.findOne({
where: {
id: req.id,
},
});
// Was user found?
if (updateUserAvatar === null) {
return res.status(200).json({
validationErrors: {
errors: [
{
msg: "Reset is invalid or has expired.",
},
],
},
});
}
// Update user with new info
models.User.update(
{
picture: req.imageUrl,
},
{
where: {
id: updateUserAvatar.dataValues.id,
},
}
);
console.log(updateUserAvatar);
At the console it should gave me a new image url but instead it just keep the old image url
Here is my profilesAPI where my route is:
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' });
}
});
And that's all for my back end code. Here is my front end that cient send image to server:
Here is the method that help user can send image to server:
const UserCard = ({ name, userEmail, isVerified, id, updateUserAvatar, currentUser }) => {
const [selectedValue, setSelectedValue] = useState("a");
const handleChange = (event) => {
setSelectedValue(event.target.value);
};
const [imageSelected, setImageSelected] = useState("");
const uploadImage = () => {
const formData = new FormData();
formData.append("file", imageSelected);
formData.append("id", id);
axios
.post("/api/v1/profiles/upload/image", formData, {
headers: { "Content-Type": "multipart/form-data" },
})
.then((response) => {
updateUserAvatar(response.data.data.imageUrl);
});
};
useEffect(() => {
if (imageSelected !== '') {
uploadImage();
}
}, [imageSelected]);
return (
<div className="avatar--icon_profile">
<Card className="profile--card_container">
<CardContent>
{currentUser.picture ? (
<div>
<input
className="my_file"
type="file"
ref={inputFile}
onChange={(e) => setImageSelected(e.target.files[0])}
/>
<div className="profile-image">
<Avatar
src={currentUser.picture}
alt="Avatar"
className="avatar--profile_image"
onClick={onButtonClick}
/>
</div>
</div>
and here is my Global State. I tried to update nested state in my context but seems like it didn't work.
const GlobalState = (props) => {
// User State -----------------------------------------------------------------------------
const [currentUser, setUser] = useState(props.serverUserData);
console.log(currentUser)
const updateUser = (userData) => {
setUser(userData);
};
// This method is passed through context to update currentUser Avatar
const updateUserAvatar = (picture) => {
setUser({ ...currentUser, picture: picture });
};
const providerValues = {
currentUser,
updateUser,
updateUserAvatar,
};
return (
<GlobalContext.Provider value={providerValues}>
{props.children}
</GlobalContext.Provider>
);
};
export default GlobalState;
and here is my console.log(currentUser) gave me:
{id: "a19cac5c-ea25-4c9c-b1d9-5d6e464869ed", name: "Nhan Nguyen", email: "nhan13574#gmail.com", publicId: "Nh1615314435848", picture: "http://res.cloudinary.com/teammateme/image/upload/v1617229506/gnlooupiekujkrreerxn.png", …}
email: "nhan13574#gmail.com"
id: "a19cac5c-ea25-4c9c-b1d9-5d6e464869ed"
isSessionValid: true
name: "Nhan Nguyen"
picture: "http://res.cloudinary.com/teammateme/image/upload/v1617229506/gnlooupiekujkrreerxn.png"
publicId: "Nh1615314435848"
__proto__: Object
Can anyone help me solve this problem? I really appreciate it
Added GlobalContext.js:
import React from "react";
const globalStateDefaults = {
modals: {
isAuthModalOpen: false,
modalToDisplay: "signup",
toggleModal: () => {},
setModalToDisplay: () => { },
},
user: undefined,
pageName: undefined,
loading: false,
teamProfileId: "",
userProfileId: "",
};
export const GlobalContext = React.createContext(globalStateDefaults);
You need to consume the context where you are trying to update user state.
const {currentUser, updateUser, updateUserAvatar} = React.useContext(GlobalContext)
Then you can call
updateUserAvatar(response.data.data.imageUrl)

formidable error: bad content-type header, unknown content-type: text/plain;charset=UTF-8

I'm receiving this error: Error: bad content-type header, unknown content-type: text/plain;charset=UTF-8 when trying to upload a photo using Formidable. When I console.log fields and files, I receive two empty objects.
What do I need to change to solve this and upload the photo?
CreatePost.js
const CreatePost = () => {
const [values, setValues] = useState({
title: "",
body: "",
photo: "",
error: "",
createdPost: "",
formData: "",
});
const { user, token } = isAuthenticated();
const {
title,
body,
error,
createdPost,
formData,
} = values;
const handleChange = (name) => (event) => {
const value = name === "photo" ? event.target.files[0] : event.target.value;
setValues({ ...values, [name]: value, formData: new FormData() });
};
const clickSubmit = (event) => {
event.preventDefault();
setValues({ ...values, error: "" });
createPost(user._id, token, formData).then((data) => {
if (data.error) {
setValues({ ...values, error: data.error });
} else {
setValues({
...values,
title: "",
body: "",
photo: "",
createdPost: data.title,
});
}
});
};
const newPostForm = () => (
<form className="mb-3" onSubmit={clickSubmit}>
<h4>Post Photo</h4>
<div className="form-group">
<label className="btn btn-secondary">
<input
onChange={handleChange("photo")}
type="file"
name="photo"
accept="image/*"
/>
</label>
</div>
<div className="form-group">
<label className="text-muted">Title</label>
<input
onChange={handleChange("title")}
type="text"
className="form-control"
value={title}
/>
</div>
<div>
<label>Post body</label>
<textarea
onChange={handleChange("body")}
value={body}
/>
</div>
<button>Create Post</button>
</form>
);
const showError = () => (
<div
style={{ display: error ? "" : "none" }}>
{error}
</div>
);
const showSuccess = () => (
<div
style={{ display: createdPost ? "" : "none" }}>
<h2>{`${createdPost} is created!`}</h2>
</div>
);
return (
<div>
<div>
{showSuccess()}
{showError()}
{newPostForm()}
</div>
</div>
);
};
export default CreatePost;
API request (create post)
export const createPost = (userId, post, token) => {
return fetch(`${API}/blog/post/${userId}`, {
method: 'POST',
headers: {
Accept: 'application/json',
Authorization: `Bearer ${token}`
},
body: post
})
.then(response => {
return response.json();
})
.catch(err => {
console.log(err);
});
};
controllers/posts.js
exports.create = (req, res) => {
let form = new formidable()
form.keepExtensions = true
form.parse(req, (err, fields, files) => {
if(err) {
console.log(err)
return res.status(400).json({
error: 'Image could not be uploaded'
})
}
const { title, body } = fields
console.log(fields)
if (!title || !body) {
return res.status(400).json({
error: "All fields are required"
})
}
let post = new Post(fields)
if(files.photo) {
if (files.photo.size > 1000000) {
return res.status(400).json({
error: "Image should be less than 1MB in size."
})
}
post.photo.data = fs.readFileSync(files.photo.path)
post.photo.contentType = files.photo.type
}
post.save((err, result) => {
if(err) {
return res.status(400).json({
error: errorHandler(err)
})
}
res.json(result)
})
})
}
exports.photo = (req, res, next) => {
if (req.post.photo.data) {
res.set('Content-Type', req.post.photo.contentType)
return res.send(req.post.photo.data)
}
next()
}
this has been up for a long time, I bet you already solve it.
If somebody stumbles with this question as I did, what worked for me was adding .IncomingForm() method on the new form in post controller file, like this:
let form = new formidable.IncomingForm();

How to send data from React front-end to Nodejs Express back-end on button click

Disclaimer: I am new to React and Nodejs and have of course looked at all the current similar posts but none of them were able to help me fully so please don't mark as duplicate.
In my Nodejs index.js I have:
app.get('/list-certs', function (req, res) {
fs.readFile("certs.json", 'utf8', function (err, data) {
console.log( data );
res.send( data );
});
})
app.post('/list-certs', function (req, res) {
res.send('POST request: ' + req.body.domain-input);
console.log('post is working!')
});
And in my React App.js I have:
componentDidMount() {
this.callApi()
.then(res => {
this.setState({
json: res.map(x => x),
})
})
.catch(err => console.log(err));
}
callApi = async () => {
const response = await fetch('/list-certs');
const body = await response.json();
if (response.status !== 200) throw Error(body.message);
return body;
};
handleChange(event) {
this.setState({domainInputValue: event.target.value});
}
click() {
}
render() {
return (
<div className="App">
<form action="http://127.0.0.1:8000/list-certs" className="add-cert-form" method="post">
<h1>Add new domain</h1>
<h5 className="domain-label">Name:</h5>
<Input className="domain-input" value={this.state.domainInputValue} onChange={(e) => {this.handleChange(e)}}></Input>
<Button onClick={this.click} className="domain-button" type='primary'>Add</Button>
</form>
.
.
.
}
How do I send the data in my input field domain-input to my back-end when the button domain-button is clicked? I know something needs to go in the click() but I'm not sure what.
Any help is appreciated!
Your click function will look like it:
async click() {
const { domainInputValue } = this.state;
const request = await fetch('/echo/json', {
headers: {
'Content-type': 'application/json'
},
method: 'POST',
body: { domainInputValue }
});
}

Resources