Home > Enterprise >  Recursive Component returning property undefined error in ReactJS
Recursive Component returning property undefined error in ReactJS

Time:05-09

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.

CodePudding user response:

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 */}
)}
  • Related