skip to Main Content

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

2

Answers


  1. Chosen as BEST ANSWER

    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


  2. If you want to trigger re-render you could declare the likePost function in the parent component and pass it to the Post component as a prop.
    Doing this, you will be able to access the setPosts method and trigger re-render.

    In the parent component:

        
        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);
            }
          };
          
          ...
    
          {
            posts.map((post) => (
              <div key={post._id} className='mb-3'>
                <Post post={post} likePost={likePost} />
              </div>
            ));
          }
    

    Then, change your Post component declaration to and remove the likePost function implementation from it:

    export default function Post({
        post,
        likePost
    }) { ... }
    

    The onChange method definition on the Rating component will remain unchanged:

    <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"/>}
            />
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search