How do I give a rating for a single post but not have to refetch all the posts.
Currently I have an array of posts that I map through. I am giving a rating for one post but have to refetch all the posts each time. Is there a way to only refetch the post I am giving the rating for?
On the Frontend I have:
const [posts, setPosts] = useState([])
useEffect(() => {
allPosts()
}, [])
const allPosts = async () => {
try {
const { data } = await axios.get(`/posts/${page}`)
setPosts(data)
} catch (err) {
console.log(err)
}
}
const likePost = async (e, newValue) => {
e.preventDefault()
setValue1(newValue)
try {
const { data } = await axios.put('/like-post', {
likeId: currentPost._id,
hover,
})
} catch (err) {
console.log(err)
}
}
In the Backend I am collecting all the posts:
export const posts = async (req, res) => {
try {
const currentPage = req.params.page || 1
const perPage = 5
const posts = await Post.find()
.skip((currentPage - 1) * perPage)
.limit(perPage)
.populate('postedBy')
.populate('comments.postedBy', '_id name image')
.sort({ createdAt: -1 })
res.json(posts)
} catch (err) {
console.log(err)
}
}
To Like the Post on the Frontend I am:
const [value1, setValue1] = useState(0)
const likePost = async (e, newValue) => {
e.preventDefault()
setValue1(newValue)
try {
const { data } = await axios.put('/like-post', {
likeId: currentPost._id,
hover,
})
allPosts()
} catch (err) {
console.log(err)
}
}
And to like on the Backend:
export const likePost = async (req, res) => {
try {
const { likeId, hover } = req.body
const user = await User.findById(req.user._id).exec()
const post = await Post.findById(likeId).exec()
// console.log(user.id)
let existingRatingObject = post.likes.find((ele) => ele.postedBy == user.id)
if (existingRatingObject === undefined) {
let ratingAdded = await Post.findByIdAndUpdate(
likeId,
{
$push: { likes: { starValue: hover, postedBy: user } },
},
{ new: true },
).exec()
res.json(ratingAdded)
} else {
// if user have already left rating, update it
const ratingUpdated = await Post.updateOne(
{ likes: { $elemMatch: existingRatingObject } },
{ $set: { 'likes.$.starValue': hover } },
{ new: true },
).exec()
res.json(ratingUpdated)
}
} catch (err) {
console.log(err)
}
}
CodePudding user response:
Since you are returning the updated post you could implement the update logic on your client to avoid refetching all data:
const likePost = async (e, newValue) => {
e.preventDefault();
setValue1(newValue);
try {
const { data } = await axios.put('/like-post', {
likeId: currentPost._id,
hover,
});
const postIndex = posts.indexOf((post) => post._id === data._id);
if (postIndex === -1) {
// Add new post
setPosts((oldData) => {
return [...oldData, data]
})
} else {
// Update post
setPosts((oldData) => {
oldData[postIndex] = data
return oldData
})
}
} catch (err) {
console.log(err);
}
};
CodePudding user response:
Below is my JSX. I map posts to post and inside the post component I have a loadLikes, showAverage function and likePost.
{posts.map((post) => (
<div key={post._id} className="mb-3">
<Post
post={post}
/>`
Then I have the following functions:
export default function post({
post,
}) {
const [state, setState] = useContext(UserContext)
const [hover, setHover] = useState(-1)
const [value1, setValue1] = useState(0)
const [postAverage, setPostAverage] = useState(0)
//UE - show likes
useEffect(() => {
if (state && state.token) {
loadLikes()
showAverage()
}
}, [state && state.token])
//V - Like Rating
const loadLikes = async () => {
console.log('load likes')
if (post.likes && state.user) {
let existingRatingObject = await post.likes.find(
(ele) => ele.postedBy == state.user._id,
)
existingRatingObject && setValue1(existingRatingObject.starValue) // current user's star
}
}
const showAverage = () => {
console.log('show average run')
if (post && post.likes && state.user) {
let likesArray = post.likes
let total = []
let length = likesArray.length
// console.log("length", length);
likesArray.map((r) => total.push(r.starValue))
let totalReduced = total.reduce((p, n) => p n, 0)
// console.log("totalReduced", totalReduced);
let highest = length * 5
// console.log("highest", highest);
let average = (totalReduced * 5) / highest
// console.log("result", result);
setPostAverage(average)
// console.log('POST AVERAGE', postAverage)
}
}
const likePost = async (e, newValue) => {
e.preventDefault()
setValue1(newValue)
setAnchorEl(null)
try {
const { data } = await axios.put('/like-post', {
likeId: currentPost,
hover,
})
socket.emit('new-post', hover)
const postIndex = posts.indexOf(currentPost)
const newRatedPost = data.ratingUpdatedDetails
setPosts((oldData) => {
oldData[postIndex] = newRatedPost
return oldData
})
} catch (err) {
console.log(err)
}
}
return (
<>
<Rating
size="large"
name="hover-feedback"
value={value1}
precision={0.5}
onChange={(e, newValue) => likePost(e, newValue)}
onChangeActive={(event, newHover) => {
setHover(newHover)
}}
onClick={() => handlePostID(post)}
emptyIcon={<StarIcon style={{ opacity: 0.55 }}fontSize="inherit"/>}
/>
<>
)
}
I have added the oldData code in like you suggested, but this doesnt rerender the mapped post prop? not sure what I can do to update this?
@Ipizzinidev