To implement a threaded (nested) comment system in React

Spread the love

To implement a threaded (nested) comment system in React with backend functionality for managing comments (approve, remove, mark as spam, edit, etc.) and storing them in a database, follow these steps:

1. Frontend (React)

a. Setting up the Comment Form:

Create a form to submit comments and replies. You may need separate components for displaying the main comment form and the reply form.

jsx
// CommentForm.jsx
import React, { useState } from 'react';
function CommentForm({ parentId = null, onSubmit }) {
const [commentText, setCommentText] = useState();

const handleSubmit = (e) => {
e.preventDefault();
onSubmit({ text: commentText, parentId });
setCommentText();
};

return (
<form onSubmit={handleSubmit}>
<textarea
value={commentText}
onChange={(e) =>
setCommentText(e.target.value)}
placeholder={parentId ? ‘Reply to this comment’ : ‘Add a comment’}
/>
<button type=“submit”>Submit</button>
</form>

);
}

export default CommentForm;

b. Displaying Comments with Threading:

To handle nested comments, you’ll need a recursive component to display comments and their replies.

jsx
// CommentList.jsx
import React from 'react';
import CommentForm from './CommentForm';
function Comment({ comment, onSubmit }) {
return (
<div style={{ marginLeft: comment.parentId ? ‘20px:0‘ }}>
<div>{comment.text}</div>
<CommentForm parentId={comment.id} onSubmit={onSubmit} />
{comment.replies && (
<div style={{ marginLeft:20px‘ }}>
{comment.replies.map((reply) => (
<Comment key={reply.id} comment={reply} onSubmit={onSubmit} />
))}
</div>
)}
</div>

);
}

function CommentList({ comments, onSubmit }) {
return comments.map((comment) => (
<Comment key={comment.id} comment={comment} onSubmit={onSubmit} />
));
}

export default CommentList;

c. Main Component to Manage Comments:

This component will handle fetching and submitting comments to the backend.

jsx
// CommentsSection.jsx
import React, { useState, useEffect } from 'react';
import CommentForm from './CommentForm';
import CommentList from './CommentList';
function CommentsSection({ postId }) {
const [comments, setComments] = useState([]);

useEffect(() => {
// Fetch comments from the backend
fetch(`/api/comments?postId=${postId}`)
.then((res) => res.json())
.then((data) => setComments(data));
}, [postId]);

const handleNewComment = (newComment) => {
// Submit new comment to the backend
fetch(‘/api/comments’, {
method: ‘POST’,
body: JSON.stringify(newComment),
headers: {
‘Content-Type’: ‘application/json’,
},
})
.then((res) => res.json())
.then((data) => setComments((prevComments) => […prevComments, data]));
};

return (
<div>
<CommentForm onSubmit={handleNewComment} />
<CommentList comments={comments} onSubmit={handleNewComment} />
</div>

);
}

export default CommentsSection;

2. Backend (Node.js with Express Example)

a. Setting up the Database:

You will need a database table to store the comments. Here’s an example schema using MySQL or any relational database:

sql
CREATE TABLE comments (
id INT AUTO_INCREMENT PRIMARY KEY,
post_id INT NOT NULL,
parent_id INT DEFAULT NULL,
user_id INT,
text TEXT,
status ENUM('pending', 'approved', 'spam', 'removed') DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (parent_id) REFERENCES comments(id) ON DELETE CASCADE
);
  • parent_id: used for nesting replies.
  • status: used to track comment status (approved, spam, etc.).

b. Setting up Express Routes:

javascript
// server.js
const express = require('express');
const bodyParser = require('body-parser');
const mysql = require('mysql');
const app = express();
app.use(bodyParser.json());

const db = mysql.createConnection({
host: ‘localhost’,
user: ‘root’,
password: ,
database: ‘blog_system’,
});

// Fetch comments (including nested replies)
app.get(‘/api/comments’, (req, res) => {
const { postId } = req.query;

db.query(
‘SELECT * FROM comments WHERE post_id = ? ORDER BY created_at’,
[postId],
(err, results) => {
if (err) throw err;

// Build nested comment structure
const comments = buildCommentTree(results);
res.json(comments);
}
);
});

// Submit a new comment
app.post(‘/api/comments’, (req, res) => {
const { text, parentId, postId } = req.body;
const userId = 1; // You can replace with actual user ID logic

db.query(
‘INSERT INTO comments (post_id, parent_id, user_id, text) VALUES (?, ?, ?, ?)’,
[postId, parentId, userId, text],
(err, result) => {
if (err) throw err;
res.status(201).json({ id: result.insertId, text, parentId });
}
);
});

// Manage comment status (approve, remove, spam, etc.)
app.patch(‘/api/comments/:id/status’, (req, res) => {
const { id } = req.params;
const { status } = req.body;

db.query(
‘UPDATE comments SET status = ? WHERE id = ?’,
[status, id],
(err) => {
if (err) throw err;
res.status(200).send(‘Comment updated’);
}
);
});

const buildCommentTree = (comments) => {
const commentMap = new Map();
const roots = [];

comments.forEach((comment) => {
comment.replies = [];
commentMap.set(comment.id, comment);

if (comment.parent_id === null) {
roots.push(comment);
} else {
const parent = commentMap.get(comment.parent_id);
parent.replies.push(comment);
}
});

return roots;
};

app.listen(3000, () => console.log(‘Server running on port 3000’));

3. Backend Management (Admin Panel)

You can create an admin panel where you can approve, remove, mark comments as spam, or edit them.

  • Use a similar PATCH endpoint to manage comment status: /api/comments/:id/status.
  • Implement the ability to edit comments by updating the text field in the database.
  • You can use React components with buttons to call these endpoints.

4. Considerations for Scalability and Performance:

  • Pagination: Use pagination for fetching large numbers of comments.
  • Caching: Cache comment data to reduce database load.
  • Spam Detection: Integrate a spam detection API to automate the “mark as spam” functionality.
  • Rate Limiting: Implement rate limiting to prevent spammy behavior on comment submission.

This approach provides a fully functional threaded comment system with both frontend and backend components.

Related Posts

Leave a Reply