2

I am currently completing a frontend mentor challenge that involves building an interactive comment section. In this project, I want the users to be able to reply to each other continuously, and hence I have used a recursive component.

I am going to be pasting the code for the two offending components

Here is my code for the Comment component

// Comment.jsx
import { useEffect } from "react";
import ReplySection from "./ReplySection";

const Comment =({content, upvotes,image,username, isUser, replies, replyMethod, createdAt, id,upvoteMethod})=> {
   useEffect(()=> {
       console.log(replies)
   })
    return(
        
        <div className="comment-container" key = {id}>
            <div className="comment">
                <div className="upvotes-section">
                    <div className="upvotes">
                        <img id="upvote" src="/images/icon-plus.svg" onClick={()=> {
                            upvoteMethod(id, "upvote")
                        }}></img>
                        <h3>{upvotes}</h3>
                        <img id="downvote" src="/images/icon-minus.svg" onClick={()=> {
                            upvoteMethod(id, "downvote")

                        }}></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" onClick={()=> {
                                replyMethod(id)
                            }}></img>
                        </div>
                    </div>

                    <div className="comment-content">
                        <h5>{content}</h5>
                    </div>
                </div>
            
            </div>

            <ReplySection reply={replies} replyMethod={replyMethod} upvoteMethod={upvoteMethod}></ReplySection>

          
        </div>
    )

}

export default Comment;

and here is the Reply Section, where all the replies for each comment are stored.

import { useEffect } from "react";
import Comment from "./Comment";
const ReplySection=({reply,replyMethod,upvoteMethod})=> {
    return(
        <div className="reply-section">
           {reply.map(el=> {
               return(
                 <Comment content = {el.content} upvotes={el.score} id={el.id} upvoteMethod = {upvoteMethod} image = {el.user.image.png} username = {el.user.username} replyMethod = {replyMethod} replies = {el.replies} createdAt={el.createdAt} isUser={ el.user.username === "juliusomo"? true :false} key={el.id}></Comment>
                  
               )
           })}
        </div>
    )

}

export default ReplySection;

and here is my App jsx file, where the object and methods I pass down are located.

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 date = new Date()
  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 upvotePost=(id, action)=> {

    let counter = 0
    action==="upvote"? counter++ : counter--;

    if (Math.abs(counter) <= 1) {
      const mult = action === "upvote" ? 1 : -1;
      setComment(
         comments => comments.map((comment) =>comment.id === id
          ? {...comment, score: comment.score + (1*mult)}
          : comment
        )
      ); 
    }  
    console.log(counter)  
  }
   
  
  
  const dateToMonth=(month)=> {
    const months = ["January","February","March","April","May","June","July","August","September","October","November","December"]

    return months[month]
  }
  const newComment=()=> {
    let comment = document.querySelector(".text-area")
    if (comment.value !="") {
      setComment([...comments, {"id":Math.random()*100000,
      "content":comment.value,
      "createdAt": `${dateToMonth(date.getMonth())}`,
      "score": 0,
      "user": {
        "image": { 
          "png": "./images/avatars/image-juliusomo.png",
          "webp": "./images/avatars/image-juliusomo.webp"
        },
        "username": "juliusomo"
      },
      "replies": []}])
    }
   

  } 

  const newReply=(id)=> {
    let commentClone = [...comments]
    for (let i =0; i<commentClone.length; i++) {
      if (commentClone[i].id == id) {
        commentClone[i].replies.push({
          "id": Math.random() * 10000,
          "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"
          }
        })
        setComment(commentClone)
      }
    } 
  }
  return (
    <div className="App">
      <CommentArea  replyMethod = {newReply} upvoteMethod ={upvotePost} comments={comments}></CommentArea>
      <TextField commentMethod={newComment}></TextField>
       
    </div>
  );
}

export default App;

when I open live server, I ended up getting this error:

Uncaught TypeError: Cannot read properties of undefined (reading 'map')
    at ReplySection (ReplySection.jsx:5:1)
    at renderWithHooks (react-dom.development.js:16398:1)
    at mountIndeterminateComponent (react-dom.development.js:21144:1)
    at beginWork (react-dom.development.js:22634:1)
    at beginWork$1 (react-dom.development.js:27597:1)
    at performUnitOfWork (react-dom.development.js:26733:1)
    at workLoopSync (react-dom.development.js:26642:1)
    at renderRootSync (react-dom.development.js:26611:1)
    at recoverFromConcurrentError (react-dom.development.js:26020:1)
    at performSyncWorkOnRoot (react-dom.development.js:26264:1)

strangely enough, when I console log the replies array in my Comment component, it just shows an array, nothing out of the ordinary.

I am stumped on this problem and would like to know what's going on. Help would be greatly appreciated.

4
  • 1
    Sounds like el.replies sometimes doesn't exist? Don't log in an effect hook, log in the component block itself Commented May 9, 2022 at 3:50
  • @CertainPerformance ive logged it in the component, it returns the appropriate array and the result is not undefined. Commented May 9, 2022 at 4:06
  • Your code shows you logging inside the hook, not inside the component Commented May 9, 2022 at 4:07
  • @CertainPerformance sorry, i meant to say that I revised my code to log in the component and still encountered the same problem. Commented May 9, 2022 at 4:16

1 Answer 1

1

Situations that would cause this are...


The first use of Comment has the replies state initialised as undefined. It might be updated later in an effect hook but until that happens, you cannot call map() on it.

The simple solution is to initialise your array states as arrays

const [replies, setReplies] = useState([]); // empty array

Some of your nested replies do not have replies properties, ie el.replies is undefined.

In these cases, you can either use optional chaining to safely call .map()...

{reply?.map((el) => (
  <Comment
    content={el.content}
    ... etc
  />
))}

or if you need more control over the layout, use conditional rendering

{reply && (
  {/* content when reply exists */}
)}
{!reply && (
  {/* content when reply does not exist */}
)}
Sign up to request clarification or add additional context in comments.

5 Comments

the optional chaining solution worked, but can you explain to me why it gets set as undefined? in the object I pass down, all the 'replies' property are clearly set to an array.
ah sorry, I just edited the post to show the App.jsx code right after I wrote my previous reply. Can you take a look at it now?
@KevinH easy, the replies with IDs 3 and 4 have no replies property
well, that was stupid. Sorry, I sometimes don't pay attention to stuff like this lol
@KevinH unexpected data is how we build fault tolerant software 😉

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.