skip to Main Content

I am studying nested comments functionality in React JS. I have trouble deleting nested comments. Basically every comment has a property replies, which is array of objects (exactly same comment object as the parent ones).

When I try to update the parent state in the inner loop (updateCommentsData(parentStateCopy);) with copied and modified array of objects it throws me this error:
updateCommentsData is not a function even though modified array of objects seems to be correct when console logging it.

State in App component:

let [commentsData, updateCommentsData] = useState();
let [currentUser, setCurrentUser] = useState();

async function getData() {
   const path = "./src/data/data.json";
   try {
     const req = await fetch(path);
     const res = await req.json();
     updateCommentsData(res.comments);
     setCurrentUser(res.currentUser);
   } catch (e) {
     console.log("Error: " + e.message);
   }
 }

Delete function:

let repliesStateCopy = [];
let parentStateCopy = JSON.parse(JSON.stringify(commentsData));

function deleteComment(id) {
  for (let i = 0; i < commentsData.length; i++) {
    if (commentsData[i].id === id) {
      updateCommentsData((prev) => {
        return prev.filter((item) => item.id !== id);
      });
      break;
    } else {
      if (commentsData[i].replies.length !== 0) {
        for (let j = 0; j < commentsData[i].replies.length; j++) {
          repliesStateCopy.push(commentsData[i].replies[j]);
          if (commentsData[i].replies[j].id === id) {
            repliesStateCopy = repliesStateCopy.filter(
              (item) => item.id !== id
            );
            parentStateCopy[i].replies = repliesStateCopy;
            updateCommentsData(parentStateCopy);
            break;
          }
        }
      }
    }
  }

JSON Data looks like that:

{
  "currentUser": {
    "image": {
      "png": "./images/avatars/image-juliusomo.png",
      "webp": "./images/avatars/image-juliusomo.webp"
    },
    "username": "juliusomo"
  },
  "comments": [
    {
      "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"
          },
          "replies": []
        },
        {
          "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"
          },
          "replies": []
        }
      ]
    },

2

Answers


  1. Chosen as BEST ANSWER

    Found the solution. The problem was that I was creating 2 separate states for parent comments and for replies and rendered them individually. Like this:

    Comments List component

    function CommentsList({ commentsData, updateCommentsData, currentUser }) {
      return (
        <>
          {commentsData &&
            commentsData.length !== 0 &&
            commentsData.map((comment) => {
              return (
                <div className="p-4" key={comment.id}>
                  <CommentsCard
                    comment={comment}
                    updateCommentsData={updateCommentsData}
                    currentUser={currentUser}
                    commentsData={commentsData}
                  />
                </div>
              );
            })}
        </>
      );
    }
    

    And then inside CommentsCard Component I would have this state:

     let [reply, updateReply] = useState(comment.replies);
    

    and render it like this:

      {reply &&
            reply.length !== 0 &&
            reply.map((reply) => {
              return (
                <div key={reply.id} className="pl-6 pt-4">
                  <CommentsCard
                    comment={reply}
                    currentUser={currentUser}
                    commentsData={commentsData}
                    updateCommentsData={updateCommentsData}
                  />
                </div>
              );
            })}
    

    Then when I tried to update the replies state it actually updated just not rendered on the screen. This delete function now does the job:

     const parentStateCopy = structuredClone(commentsData);
      function deleteComment(id) {
        for (let i = 0; i < commentsData.length; i++) {
          if (commentsData[i].id === id) {
            updateCommentsData((prev) => {
              return prev.filter((item) => item.id !== id);
            });
            break;
          } else {
            if (commentsData[i].replies.length !== 0) {
              for (let j = 0; j < commentsData[i].replies.length; j++) {
                if (commentsData[i].replies[j].id === id) {
                  parentStateCopy[i].replies = parentStateCopy[i].replies.filter(
                    (item) => item.id !== id
                  );
                  updateCommentsData(parentStateCopy);
                  break;
                }
              }
            }
          }
        }
      }
    

    and this is how I render the comments and replies in CommentsList component now:

    function CommentsList({ commentsData, updateCommentsData, currentUser }) {
      return (
        <>
          {commentsData &&
            commentsData.length !== 0 &&
            commentsData.map((comment) => {
              return (
                <div className="p-4" key={comment.id}>
                  <CommentsCard
                    comment={comment}
                    updateCommentsData={updateCommentsData}
                    currentUser={currentUser}
                    commentsData={commentsData}
                  />
                  {comment.replies &&
                    comment.replies.length !== 0 &&
                    comment.replies.map((reply) => {
                      return (
                        <div key={reply.id} className="pl-6 pt-4">
                          <CommentsCard
                            comment={reply}
                            updateCommentsData={updateCommentsData}
                            currentUser={currentUser}
                            commentsData={commentsData}
                          />
                        </div>
                      );
                    })}
                </div>
              );
            })}
        </>
      );
    }
    

  2. I think problem is updateCommentsData is being called directly with parentStateCopy rather than using the state setter function’s callback pattern (prev => newState).

    Try this code :

      const deleteComment = (commentId) => {
      setComments(prevComments => {
        const deleteRecursively = (comments) => {
          return comments.reduce((acc, comment) => {
            if (comment.id === commentId) return acc;
            return [...acc, {
              ...comment,
              replies: deleteRecursively(comment.replies)
            }];
          }, []);
        };
        
        return deleteRecursively(prevComments);
      });
    };
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search