project image

Create nested comment section with React 

Overview

Comment sections are a fundamental feature in many web applications, fostering engagement and interaction among users. Implementing nested comment sections adds depth and organization to discussions, allowing for threaded conversations.

In modern web applications, engaging user interaction through comment sections is a vital aspect. One common feature is the ability to nest comments within replies, forming a hierarchical structure that allows for better organization and readability. In this tutorial, we'll explore how to build a nested comment section using React.

 

Getting Started

 

To begin, ensure you have Node.js and NPM installed on your system. We'll be using React, a popular JavaScript library for building user interfaces. Let's dive into the step-by-step process of creating a nested comment section.

 

Setting Up the React App

 

First, create a new React application using create-react-app:

 

npx create-react-app nested-comments-app --template typescript
cd nested-comments-app

 

Next, navigate into the project directory and install any additional dependencies:

 

cd nested-comments-app
npm install

 

Implementing the Nested Comment Component

 

Within the React application, create a component named Comment responsible for rendering individual comments and their nested replies. Here's an example of the Comment component:

 

// Comment.tsx

import React from "react";

export interface CommentProps {
  comment: {
    id: string;
    comment: string;
    replies?: {
      id: CommentProps["comment"]["id"];
      comment: CommentProps["comment"]["comment"];
      replies?: CommentProps["comment"]["replies"];
    }[];
  };
  onReplyClick?: (id?: string) => void;
  onReply?: (parentId: string, val: string) => void;
  onDelete?: (parentId: string, val?: string) => void;
  replyingID?: string;
}

export function Comment({
  comment,
  onReplyClick,
  replyingID,
  onReply,
  onDelete,
}: CommentProps) {
  return (
    <ul>
      <li>
        <div style={{ display: "flex", alignItems: "center" }}>
          <span>{comment.comment}</span>
        </div>

        {replyingID === comment.id ? (
          <form
            onSubmit={(ev) => {
              ev.preventDefault();
              const data = new FormData(ev.target as HTMLFormElement).get(
                "comment"
              );
              if (data) {
                onReply?.(comment.id, data as string);
              }
            }}
          >
            <input name="comment" />
            <input type="submit" value="submit" />
            <button onClick={() => onReplyClick?.()}>Cancel</button>
          </form>
        ) : (
          <>
            <span onClick={() => onReplyClick?.(comment.id)}>reply</span>
            <span>{"  "}</span>
            <span onClick={() => onDelete?.(comment.id, undefined)}>
              Delete
            </span>
          </>
        )}
        {comment.replies?.map(
          (reply) =>
            reply && (
              <Comment
                key={reply.id}
                comment={reply}
                onReplyClick={onReplyClick}
                replyingID={replyingID}
                onReply={onReply}
                onDelete={onDelete}
              />
            )
        )}
      </li>
    </ul>
  );
}

 

Creating Nested Comments

 

Now, let's integrate the Comment component into the App component to display a list of nested comments:

 

// App.tsx

import React from "react";
import "./styles.css";
import { Comment, type CommentProps } from "./Comment";

function findNestedObj(
  entireObj: any,
  keyToFind: string,
  valToFind: string,
  message?: string,
  isDelete?: boolean
) {
  const uniqueId = (Math.random() * 100000).toString();
  const foundObj = JSON.stringify(entireObj, (_, nestedValue) => {
    if (nestedValue && nestedValue[keyToFind] === valToFind) {
      if (isDelete) {
        nestedValue = undefined;
      } else {
        if (nestedValue.replies) {
          nestedValue.replies?.push({
            id: uniqueId,
            comment: message,
          });
        } else {
          nestedValue.replies = [
            {
              id: uniqueId,
              comment: message,
            },
          ];
        }
      }
    }
    return nestedValue;
  });

  return JSON.parse(foundObj);
}

function App() {
  const [comments, setComments] = React.useState<CommentProps["comment"][]>([
    {
      id: "1",
      comment: "test1",
      replies: [
        {
          id: "3",
          comment: "test3",
          replies: [
            {
              id: "5",
              comment: "test5",
            },
          ],
        },
      ],
    },
    {
      id: "2",
      comment: "test2",
      replies: [
        {
          id: "4",
          comment: "test4",
        },
      ],
    },
  ]);
  const [replyingId, setReplyingId] =
    React.useState<CommentProps["comment"]["id"]>();

  const attachReply: CommentProps["onReply"] = (parentId, comment) => {
    const out = findNestedObj(comments, "id", parentId, comment);
    setComments(out);
    setReplyingId(undefined);
  };

  const deleteComment: CommentProps["onDelete"] = (parentId, comment) => {
    const out = findNestedObj(comments, "id", parentId, comment, true);
    setComments(out);
  };

  return (
    <div className="App">
      {comments.map(
        (comment) =>
          comment && (
            <Comment
              key={comment.id}
              comment={comment}
              onReplyClick={(id) => {
                setReplyingId(id);
              }}
              replyingID={replyingId}
              onReply={attachReply}
              onDelete={deleteComment}
            />
          )
      )}
      {!replyingId && <form
        onSubmit={(ev) => {
          ev.preventDefault();
          const form = new FormData(ev.target as HTMLFormElement);
          const data = form.get("new_comment");
          if (data) {
            setComments((prev) => {
              const updated = [...prev];
              updated.push({
                id: (Math.random() * 100000).toString(),
                comment: data as string,
              });
              (ev.target as HTMLFormElement).reset();
              return updated;
            });
          }
        }}
      >
        <input name="new_comment" />
        <input type="submit" value="submit" />
      </form>}
    </div>
  );
}

export default App;

 

Final Thoughts

 

Congratulations! You've successfully implemented a nested comment section using React. This structure allows users to interact with comments and replies hierarchically, enhancing the user experience within your web application.

 

 Feel free to extend this functionality by adding features like comment submission, replying to specific comments, or integrating it with a backend service for data storage.

 

In conclusion, creating nested comment sections using React enables a more organized and user-friendly interface, fostering better engagement and communication among users.

 

I hope this guide helps you in implementing nested comment sections within your React applications. Happy coding!