0

A week ago, I posted a question on how to implement a "Like" feature for each individual post of my Mern stack CRUD app. I didn't receive any answers, but thanks to Reddit, a friendly experienced programmer helped me connect my ReactJS frontend, ExpressJS backend, and MongoDB all together for it to work beautifully.

Problem now is, although I can save Likes from my ReactJS frontend into my MongoDB...the dynamic display of my Like counts on the frontend is still having issues. If I create two posts, and press the Like button on one post, it turns all the Like counts on every post back to 0—except the post that i'm clicking the Like button on. When I hit refresh, all the likeCounts from my backend come back fine--but all get turned to 0 once again--when I click a Like button on an individual post...My code below....

import React, {useState} from 'react'
import Axios  from 'axios';
import Space from './images/space.jpg'

function ExperienceLikes({setListExperience, listExperience, picClicked}) {
    let initLikes = new Array(listExperience.length).fill(0)
    const [likes, setLikes] = useState(initLikes)
    const [liked, setLiked] = useState(false)

    console.log(initLikes)

    const cardStyles = {
        background: "#ffffff",
        display: "flex",
        flexDirection: "column",
        justifyContent: "center",
        width: "161px",
        height: "180px",
        marginLeft: "30px",
        marginRight: "30px",
        marginTop: "20px",
        position: "relative",
    }

    const updateLike = (id, x) => {
        let tempLikes = initLikes;
        Axios.put(`http://localhost:9001/${id}/likePost`)
            .then((response) => {
                    setListExperience((previous) => {
                        return previous.map((hmm) => {
                            return hmm.id === id ? id : hmm
                        })
                    })
                    setLiked(true)
                    tempLikes[x] = tempLikes[x] + response.data.likeCount
                    setLikes(tempLikes)
                    
                }
            )
    }

    const deletePost = (id) => {
        Axios.delete(`http://localhost:9001/${id}/delete`)
            .then(() => {
                setListExperience(listExperience.filter((idx) => {
                    return idx._id != id
                }))
            })
    }

    return(
        <>
        {listExperience.map((x, id) => 
                    <div key={x._id} className={`experience--card$`} style={cardStyles}>
                        <div className='locate'>
                            {x.location}
                        </div>
                        <div className='image' style={ { display: "flex", justifyContent: "center"} } >
                                <img src={Space} alt='pic' style={ { maxWidth: "100%", maxHeight:"100%", overflow: "hidden", marginBottom: "20px"} } height={125} />
                        </div> 
                        
                        <div className={picClicked === true ?'likeBtnContainer-hidden' : 'likeBtnContainer'}>
                            <button className='likeBtn' type='button' onClick={() => updateLike(x._id, id)}>Like {liked == true ? likes[id] : x.likeCount}</button>
                        </div>
                        <div className='deleteBtnContainer'>
                            <button className='deleteBtn' type='button' onClick={() => deletePost(x._id)}>Delete</button>
                        </div>
                    </div>
            )}
        </>
    )
}

export default ExperienceLikes

In the code above, the problem I feel like i'm having is maybe something to do with my [Liked, setLiked] boolean. But I just don't know how to correct it. What do I have to do so that Likes only increment on one post, while the likeCounts remain the same on the other posts?

Below, is the code to my other component where my posts are getting created...

import React, { useState, useEffect} from 'react';
import './Experiences.css'
import Axios from 'axios';
import ExperienceLikes from './ExperienceLikes';

function Experiences() {
    const [listExperience, setListExperience] = useState([]);
    const [location, setLocation] = useState("")
    const [picClicked, setPickClicked] = useState(false)
    const [viewLocation, setViewLocation] = ("")

    const createExperience = (e) => {
        e.preventDefault();

        Axios.post('http://localhost:9001/', {location})
            .then((response) => {
                setListExperience([...listExperience,{_id: response.data._id, location, likeCount: response.data.likeCount}]);
            })  
    }    

    useEffect(() => {
        Axios.get('http://localhost:9001/')
            .then((response) => {
                setListExperience(response.data)
            })
    }, []);

return (
    <>
    <div className='form--contain'>
        <div className='form--card'>
            <form onSubmit={createExperience}>
                <textarea type='text' onChange={(event) => {setLocation(event.target.value)}} />
                <button type='submit'>Submit</button>
            </form>
        </div>
    </div>    
        <div className='experience--container'>
            <ExperienceLikes setListExperience={setListExperience} picClicked={picClicked} listExperience={listExperience} />
        </div>
    </>
)
}

export default Experiences
  • Im not seeing the place where the "likes" are loaded (count of likes for post). Probably that comes from upper level component with `listExperience`. If, the count of likes is tored in that array - why do you need to use additional `[likes, setLikes]`? Rely on 1 single data souce. Next - what exactly is `setListExperience` doing in the .then method of Axios? – Sergey Sosunov Sep 14 '22 at 20:03
  • @SergeySosunov I just added the upper level component at the bottom. The reason i'm using [likes, setLikes] is because i'm trying to make the count increments dynamic, so that I don't have to refresh to see the Like count from DB. How would I go about making that 1 single data source dynamic, without having to refresh to see the Like count??? As in, when the User hits the like Button, it automatically increments without having to refresh...how do I do that with the 1 single data source? – Proper_poe Sep 14 '22 at 20:14
  • @SergeySosunov And if needed, I can post my Express.js controllers and MongoDB schema, if you need to see everything... – Proper_poe Sep 14 '22 at 20:23

2 Answers2

0

You don't need [Liked, setLiked] state. You can just update the listExperience state when the user press the like button.

  • I just posted the listExperience state at the bottom of the page. Is there any example of what I have to change or switch around, for the like count from that listExperience state to be dynamic??? And update/incremement as it's clicked, without having to refresh the page? – Proper_poe Sep 14 '22 at 20:15
  • you can update the state like this setListExperience((previous) => { return previous.map((hmm) => { return hmm.id === id ? {...hmm, likeCount: hmm. likeCount +1} : hmm }) }) – Rohit Chhabra Sep 14 '22 at 20:23
  • Thank you, but I still have the same problem, where the likes only update after refresh. I'm trying to get the likes to update as they're clicked, without a refresh, which is why I had the [likes, setLikes]. Is there a way I can update the Likes as they're clicked, without having to refresh? – Proper_poe Sep 14 '22 at 20:29
0

You can achieve your goal with less code than you expect.

You have listExperience array. Each item in this array has a likeCount property afaics. Correct? Now, you are using .map to render your items from this array.

Now, lets take a look at the updateLike method. You made it accept the id of the item and the index of this item in the array, which you named id and in the function you called it x. Tt is a bad naming, to be honest, a bit idx will match better. Now, what you need to do - is to make this method to accept the (item), your x.

So make it looks like this:

const updateLike = (experience) => {
  Axios.put(`http://localhost:9001/${experience._id}/likePost`).then(
    (response) => {
      // Based on your code I assume that response.data really returns likeCount
      // So just update the passed item's likeCount
      experience.likeCount = response.data.likeCount;

      // Question about where and how you are storing the data about if the item is liked
      // check for if null or undefined
      experience.isLiked =
        experience.isLiked == null ? true : !experience.isLiked;

      // We mutated the item from the array, now we need to update the reference
      // of this array so React will detect changes.
      setListExperience((current) => [...current]);
    }
  );
};

And now update your <button /> a bit

<button
  className="likeBtn"
  type="button"
  onClick={() => updateLike(x)}
>
  {/* I have no idea what was the idea of this line below */}
  Like {liked == true ? likes[id] : x.likeCount}
</button>
Sergey Sosunov
  • 4,124
  • 2
  • 11
  • 15
  • I want to give this a try...but where would does the isLiked come from??? Do I need to put that in my MongoDB? – Proper_poe Sep 14 '22 at 20:46
  • @Proper_poe Thats a good question, it should come from MongoDb, you should dig in that [area](https://stackoverflow.com/questions/45041511/how-do-i-decide-whether-a-post-is-liked-by-a-user-with-mongoose), ideally your backend should return information about `experience` including `isLiked` property, aside with `likesCount`. And I guess you understand, that this `isLiked` will be different for different users. That will be a long story :) Also, just to note, im not a MERN dev, im react/angular + node/aspnet + postgres/mssql/firebase dev, no mongo :) – Sergey Sosunov Sep 14 '22 at 20:50