Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rendering nested/threaded comments in React

Given the below array, I'd like to render comments in a threaded manner by making use of parentId.

comments: [
    {
      id: 1,
      parentId: null
    },
    {
      id: 2,
      parentId: 1
    },
    {
      id: 3,
      parentId: 1
    },
    {
      id: 4,
      parentId: 3
    },
    {
      id: 5,
      parentId: 4
    }
  ]

I thought with the below components I'd be able to recurse through the comments, but the output is not what I'd expect (it seems to be rendering a new <ul> element for every comment.) I'm a bit new to react and javascript, so maybe I'm not implementing the recursion correctly, or should comments be structured differently?

const Comment = (props) => (
  <li>
    {props.comment.id}
    {props.comment.children.length > 0 ?
      <Comments comments={props.comment.children}/>
      : null }
  </li>
);

const Comments = (props) => (
  <ul>
    {props.comments.map((comment) => {
      comment.children = _.filter(props.comments, {'parentId': comment.id});
      return <Comment key={comment.id} comment={comment}/>
    })}
  </ul>
);
like image 581
ChrisArmstrong Avatar asked Apr 24 '16 22:04

ChrisArmstrong


2 Answers

If you turn that list into a structure which actually reflects the nested hierarchy of the comments, then you'll have an easier time building a component for rendering them.

[
  {
    id: 1,
    children: [
      { id: 2, children: [] },
      { id: 3, children: [ ... ] }
    ]
  }
]

You could implement a function to do the conversion.

function nestComments(commentList) {
  const commentMap = {};

  // move all the comments into a map of id => comment
  commentList.forEach(comment => commentMap[comment.id] = comment);

  // iterate over the comments again and correctly nest the children
  commentList.forEach(comment => {
    if(comment.parentId !== null) {
      const parent = commentMap[comment.parentId];
      (parent.children = parent.children || []).push(comment);
    }
  });

  // filter the list to return a list of correctly nested comments
  return commentList.filter(comment => {
    return comment.parentId === null;
  });
}

Here's an idea for how you could go from that flat structure to a list of nested comments. Once you're done with that implementation, all you'd need would be a recursive React component.

function Comment({ comment }) {
  const nestedComments = (comment.children || []).map(comment => {
    return <Comment comment={comment} />;
  });

  return (
    <div key={comment.id}>
      <span>{comment.text}</span>
      <a href={comment.author.url}>{comment.author.name}</a>
      {nestedComments}
    </div>
  );
}
like image 56
Dan Prince Avatar answered Oct 15 '22 01:10

Dan Prince


in case you need an example that needs to go unknown levels deep i solved with this

function Comment({text, author}){
  return <div>{author}: {text}</div>
}

CommentTree(comments) {

  let items = comments.map((comment) => {
    return (
      <div className="border-l pl-6">
        <Comment
          key={comment.id}
          text={comment.text}
          author={comment.author}
        />
        {comment.children && CommentTree(comment.children)}
      </div>
    )
  })

  return items
}
like image 34
Chris Wallace Avatar answered Oct 15 '22 00:10

Chris Wallace