medium+210 XP
Build collapsible nested comments
Render a fixed comment thread: two top-level comments, the first of which has a reply, which itself has a reply (three levels deep).
Each comment with replies gets a toggle that collapses or expands its own direct replies — and collapsing must hide everything nested underneath them too, not just the direct children.
Requirements
- Every comment renders as `<div data-testid="comment" data-comment-id="ID">`.
- A comment with replies has a button `data-testid="toggle-ID"` reading "Hide replies (N)" when expanded and "Show replies (N)" when collapsed, where N is its number of direct replies.
- Collapsing a comment removes it and every one of its descendants from the DOM entirely — not just visually hidden.
- Comments with no replies get no toggle button at all.
Your workspace
const COMMENTS = [ { id: 1, text: "First comment", replies: [ { id: 2, text: "Reply to first", replies: [{ id: 3, text: "Reply to reply", replies: [] }], }, ], }, { id: 4, text: "Second comment", replies: [] }, ]; function Comment({ comment }) { // TODO: clicking the toggle button should hide/show this comment's // direct replies (and everything nested inside them), and the label // should read "Hide replies (N)" / "Show replies (N)" where N is the // number of DIRECT replies. const hasReplies = comment.replies.length > 0; return ( <div data-testid="comment" data-comment-id={comment.id} style={{ marginLeft: 16, marginTop: 8 }}> <p>{comment.text}</p> {hasReplies ? ( <button data-testid={`toggle-${comment.id}`}> Hide replies ({comment.replies.length}) </button> ) : null} <div data-testid={`replies-${comment.id}`}> {comment.replies.map((reply) => ( <Comment key={reply.id} comment={reply} /> ))} </div> </div> ); } export default function App() { return ( <div style={{ fontFamily: "sans-serif", padding: 16 }}> {COMMENTS.map((comment) => ( <Comment key={comment.id} comment={comment} /> ))} </div> ); }
Ready to check it?
5 tests run against your rendered component, right here in your browser. Sign in to claim the XP when you pass.
Stuck? The react course covers the ideas this problem is built on.