1

I am currently trying to create an interactive comment section that allows users to comment, reply, and like/dislike the comments of others. I am currently stuck on the likes/upvote section, as I am unable to update the value of likes in my component.

Here is my Comment component (which represents a single comment in the comment section):

import { useState } from "react";

const Comment = ({
  upvotes,
  image,
  username,
  isUser,
  replies,
  createdAt,
  id,
  upvoteMethod,
}) => {
  const [score, setScore] = useState(upvotes);
  return (
    <div className="comment-container">
      <div className="comment">
        <div className="upvotes-section">
          <div className="upvotes">
            <img
              id="upvote"
              src="/images/icon-plus.svg"
              onClick={() => {
                upvoteMethod(id, "upvote");
              }}
            ></img>
            <h3>{score}</h3>
            <img
              id="downvote"
              src="/images/icon-minus.svg"
              onClick={upvoteMethod}
            ></img>
          </div>
        </div>
        <div className="comment-side">
          <div className="comment-header">
            <div className="profile">
              <img src={image}></img>
              <h5>{username}</h5>
              <h6 className="created-at">{createdAt}</h6>
            </div>
            <div className="options">
              <img src={isUser ? "images/icon-delete.svg" : ""}></img>
              <img src="/images/icon-reply.svg"></img>
            </div>
          </div>
        </div>
      </div>

      {replies.map((r) => {
        return (
          <div className="replies-section">
            <img src={isUser ? "images/icon-delete.svg" : ""}></img>
          </div>
        );
      })}
    </div>
  );
};

export default Comment;

And here is the App.jsx file, where I pass down the update function for my upvote counter:

import Comment from "./components/Comment";
import "./App.css";
import CommentArea from "./components/CommentArea";
import TextField from "./components/TextField";
import { useState } from "react";

function App() {
  let displayedComments = [
    {
      id: 1,
      content:
        "Impressive! Though it seems the drag feature could be improved. But overall it looks incredible. You've nailed the design and the responsiveness at various breakpoints works really well.",
      createdAt: "1 month ago",
      score: 12,
      user: {
        image: {
          png: "./images/avatars/image-amyrobson.png",
          webp: "./images/avatars/image-amyrobson.webp",
        },
        username: "amyrobson",
      },
      replies: [],
    },
    {
      id: 2,
      content:
        "Woah, your project looks awesome! How long have you been coding for? I'm still new, but think I want to dive into React as well soon. Perhaps you can give me an insight on where I can learn React? Thanks!",
      createdAt: "2 weeks ago",
      score: 5,
      user: {
        image: {
          png: "./images/avatars/image-maxblagun.png",
          webp: "./images/avatars/image-maxblagun.webp",
        },
        username: "maxblagun",
      },
      replies: [
        {
          id: 3,
          content:
            "If you're still new, I'd recommend focusing on the fundamentals of HTML, CSS, and JS before considering React. It's very tempting to jump ahead but lay a solid foundation first.",
          createdAt: "1 week ago",
          score: 4,
          replyingTo: "maxblagun",
          user: {
            image: {
              png: "./images/avatars/image-ramsesmiron.png",
              webp: "./images/avatars/image-ramsesmiron.webp",
            },
            username: "ramsesmiron",
          },
        },
        {
          id: 4,
          content:
            "I couldn't agree more with this. Everything moves so fast and it always seems like everyone knows the newest library/framework. But the fundamentals are what stay constant.",
          createdAt: "2 days ago",
          score: 2,
          replyingTo: "ramsesmiron",
          user: {
            image: {
              png: "./images/avatars/image-juliusomo.png",
              webp: "./images/avatars/image-juliusomo.webp",
            },
            username: "juliusomo",
          },
        },
      ],
    },
  ];

  const [comments, setComment] = useState(displayedComments);
  const updateComments = (event) => {
    setComment(comments);
  };

  const upvotePost = (id, action) => {
    for (let i = 0; i < comments.length; i++) {
      if (comments[i].id == id) {
        action === "upvote" ? comments[i].score++ : comments[i]--;
        setComment(comments);
        console.log(action);
      }
    }
  };

  return (
    <div className="App">
      <CommentArea upvoteMethod={upvotePost} comments={comments}></CommentArea>
      <TextField></TextField>
    </div>
  );
}

export default App;

When I try to console log the comments array after using setState(), it shows that the value has updated after clicking the buttons, but it does not show on the UI.

for reference here is a screenshot of me debugging in the console (upvotes is comments.score, and I clicked on the first comment/the one with 12 upvotes)

enter image description here

Can anyone help me understand what is going on and how to fix it? help would be appreciated.

Danry
  • 147
  • 15
Kevin H
  • 111
  • 1
  • 8
  • Is there a reason why you put `upvotes` into a `score` state: `useState(upvotes)`? Your component's state is set on the first render, so `score` will only take the initial value of `upvotes`. It doesn't look like you're using `setScore`, so you be able to remove this state and instead use `upvotes` instead of `score` – Nick Parsons May 07 '22 at 13:25
  • @NickParsons ah yeah, I wasn't sure why I did that either. Unfortunately, removing the state and using the value for upvotes did not solve my issue. – Kevin H May 07 '22 at 13:28
  • `comments[i].score++` is also not how you should be updating your state in create. This updates the comments state in place, so react isn't able to detect any changes when you pass the same `comments` object back: `setComment(comments)` – Nick Parsons May 07 '22 at 13:38
  • @NickParsons how should I update it? – Kevin H May 07 '22 at 13:40

1 Answers1

2

State updates in reactjs should be immutable, you can think of this as meaning that you shouldn't update/mutate your current state in any way, but rather, create a new state value and apply your changes to that. You're currently mutating your state and then setting your state back to the mutated state value:

action==="upvote" ? comments[i].score++ : comments[i]--
setComment(comments)

From React's perspective, the new comments array that you're setting as your state is the same as your old comments state (ie: if you were to compare the old state and the new state with ===, you would get back true), so React doesn't rerender as it can't see the state change. Instead, you should be creating a new comments array (below this is created with .map()), and then creating new inner comment objects when you want to update the score value:

const upvotePost=(id, action)=> {
  const mult = action === "upvote" ? 1 : -1;
  setComment(
    comments => comments.map(comment => comment.id === id
      ? {...comment, score: comment.score + (1*mult)}
      : comment
    )
  ); 
}

Notice that above we never update the comments state directly, but instead return a new array, with a new object for the ones we want to update. The new object is created by using {...comment, score: comment.score + (1*mult)}, where the ...comment adds all of the (own enumerable) keys from the current object (see the spread syntax ...), and then sets the score key on the newly created object to an updated value. The arrow function in the setComments() is being used to obtain the most up to date version of comments before we update it - see functional updates.

Another issue is that you don't need your score state in your Comment component. The useState(upvotes) sets the score value to the value of upvotes on the intial render of your component, but when the upvotes prop changes, your score state won't change with it. As you're not using setScore, you can remove this state and instead use upvotes directly.

Nick Parsons
  • 45,728
  • 6
  • 46
  • 64
  • this solution worked, but I am still confused about some things. for the comments => comments.map() part of your answer, I have never seen it being used in js, especially in a setState function. Essentially, is this just a way of saying "return this value after it has been passed through the map function"? – Kevin H May 07 '22 at 14:07
  • @KevinH It is essentially saying grab the most up to date version of `comments` (that's done by the argument of the arrow function `comments =>`) and set comments state to whatever we return from the arrow function, that being a mapped array of the current comments state: `comments.map(...)`. – Nick Parsons May 07 '22 at 14:13
  • 1
    ah alright, thanks for clarifying and answering my question – Kevin H May 07 '22 at 14:15
  • @KevinH No worries, [here](https://reactjs.org/docs/hooks-reference.html#functional-updates) is the associated section from the docs about this if you want to take a further look. – Nick Parsons May 07 '22 at 14:30
  • Actually I have one more question regarding your answer if you dont mind. In your answer, it says that "From React's perspective, the new comments array that you're settig as your state is the same as your old comments state". If that is the case, then how is it that when I console log the comments array after I used the setComment() function, the score changes? (refer to the pic in my answer) – Kevin H May 07 '22 at 14:45
  • @KevinH Even though you've changed your state, the objects and the array still refer to the same objects/arrays in memory as they did before, since all you've done is update the score property and not create any new arrays or objects. So when you pass your `comments` array to `setComment()`, it still thinks its the same state as before as the array you've passed still refers to the same array in memory as before. [This fiddle](https://jsfiddle.net/rmcd160v/) might give your a better idea. – Nick Parsons May 07 '22 at 14:57
  • @KevinH you can also find a more detailed explanation [here](https://stackoverflow.com/q/37755997/5648954) (this question uses class components, but it's the same idea) – Nick Parsons May 08 '22 at 02:15