# Nested Comments > Render a threaded comment list and implement reply, edit, delete, and collapse with immutable recursive tree updates. - Difficulty: intermediate - Tags: useState, recursion, forms, accessibility - Estimated time: 75 minutes - Tests: 13 - Requires subscription: yes ## Overview Build a threaded comments UI: render comments recursively and add reply, edit, delete, and collapse with immutable tree updates. ## Requirements ### Render - Render the full thread from `initialComments` in `App.tsx`; the provided `CommentCard.tsx` and `data.ts` are read-only. - Root comments appear in array order and replies are nested inside their parent, so a parent comment container also contains its descendants in the DOM. - Each comment container has `data-testid="comment-"`. The seed order is `c1`, `c2`, `c3`, `c4`, `c5`, where `c1` contains `c2` and `c4`, and `c2` contains `c3`. - Each comment shows its author and text. The text element has `data-testid="comment-text-"`. ### Collapse - Only comments that have replies render a collapse control `data-testid="toggle-"` with an `aria-expanded` attribute. - Clicking the collapse control hides or shows only that comment's replies. The comment itself stays visible. ### Reply - Every comment renders `data-testid="reply-"`, `data-testid="edit-"`, and `data-testid="delete-"` controls. - Clicking `reply-` opens a reply form under that comment with `data-testid="reply-input-"`, `data-testid="reply-submit-"`, and `data-testid="reply-cancel-"`. - Submitting a non-empty reply appends it to that comment's replies with author `"You"`, and closes the form. - Reply text is trimmed, and empty or whitespace-only submissions are rejected: the form stays open and nothing is added. ### Edit - Clicking `edit-` replaces the comment text with a prefilled editor: `data-testid="edit-input-"`, `data-testid="edit-save-"`, and `data-testid="edit-cancel-"`. - Saving an edit trims the text and updates only that comment, then closes the editor. - Cancelling an edit restores the original text and closes the editor. ### Delete - Deleting a comment removes it together with its entire subtree while leaving siblings intact. - Deleting every comment renders `data-testid="empty-state"`. ### Immutability - All updates are immutable: neither `initialComments` nor any node's `replies` array is mutated. ## Notes - The card UI and its recursive rendering are already provided by the read-only `CommentCard`; you own the state, the handlers, and the two tree helpers. - The single most important idea is a recursive tree update. `map` over the current level, recurse into `replies` when the target is not the current node, and rebuild only the nodes on the path: ```ts function updateComment( comments: Comment[], id: string, update: (comment: Comment) => Comment, ): Comment[] { return comments.map((comment) => comment.id === id ? update(comment) : { ...comment, replies: updateComment(comment.replies, id, update) }, ); } ``` Use it for both reply (the `update` fn appends a new reply) and edit (the `update` fn returns `{ ...comment, text }`). - Never mutate `initialComments` or a node's `replies` array. Create new arrays with spread; the seed is shared across renders and across tests. - Collapse is UI state, separate from the tree. Keep the collapsed ids in a `Set`; `CommentCard` reads it to hide replies. - Delete drops the whole subtree: `filter` out the node and recurse into the remaining replies so the deleted branch never renders. - Make the reply and edit textareas controlled and `trim()` before saving; reject empty or whitespace-only text so the form stays open. - A reply added to a collapsed parent stays hidden until you expand it — that is expected and fine. - APIs: [Updating objects in state](https://react.dev/learn/updating-objects-in-state), [Rendering lists](https://react.dev/learn/rendering-lists), [`aria-expanded`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-expanded). ## Tests 01. renders all seed comments with replies nested under their parent 02. shows the author and text of every comment 03. collapses a comment's replies and keeps the comment visible 04. expands a collapsed comment again 05. opens the reply form under the correct comment 06. adds a reply with the author You and closes the form 07. rejects an empty or whitespace-only reply and keeps the form open 08. prefills the editor with the current comment text 09. saves an edited comment and closes the editor 10. cancels an edit and restores the original text 11. deletes a comment and its replies, keeping siblings intact 12. shows the empty state when every comment is deleted 13. never mutates the imported seed tree