Server

// routes
router.get("/comments/:page", requireSignin, isAdmin, comments);
router.get("/comment-count", commentCount);
router.delete(
  "/comment/:commentId",
  requireSignin,
  canUpdateDeleteComment,
  removeComment
);
// middleware
export const canUpdateDeleteComment = async (req, res, next) => {
  try {
    const { commentId } = req.params;
    const comment = await Comment.findById(commentId);

    // you get req.user._id from verified jwt token
    const user = await User.findById(req.user._id);
    console.log("canUpdateDeleteUser comment ===> ", comment, user);
    switch (user.role) {
      case "Admin":
        next();
        break;
      case "Author":
        if (comment.postedBy.toString() === req.user._id.toString()) {
          next();
        }
        break;
      case "Subscriber":
        if (comment.postedBy.toString() === req.user._id.toString()) {
          next();
        }
        break;
      default:
        return res.status(400).send("Unauthorized");
    }
  } catch (err) {
    console.log(err);
  }
};
// controllers
export const comments = async (req, res) => {
  try {
    console.log(req.params);
    const perPage = 3;
    const page = req.params.page ? req.params.page : 1;

    const comments = await Comment.find({})
      .skip((page - 1) * perPage)
      .populate("postedBy", "_id name")
      .populate("postId", "title slug")
      .sort({ createdAt: -1 })
      .limit(perPage)
      .exec();
    return res.json(comments);
  } catch (err) {
    console.log(err);
    res.sendStatus(400);
  }
};

export const commentCount = async (req, res) => {
  try {
    const count = await Comment.countDocuments();
    return res.json(count);
  } catch (err) {
    console.log(err);
    res.sendStatus(400);
  }
};

export const removeComment = async (req, res) => {
  try {
    const { commentId } = req.params;
    const comment = await Comment.findByIdAndDelete(commentId);
    return res.json({ ok: true });
  } catch (err) {
    console.log(err);
    res.sendStatus(400);
  }
};

Client

import { useState, useEffect, useContext } from "react";
import AdminLayout from "../../../components/layout/AdminLayout";
import { Row, Col, Button, List, Input } from "antd";
import axios from "axios";
import { AuthContext } from "../../../context/auth";
import toast from "react-hot-toast";
import Link from "next/link";
import dayjs from "dayjs";
var localizedFormat = require("dayjs/plugin/localizedFormat");

dayjs.extend(localizedFormat);

const Comments = () => {
  // context
  const [auth, setAuth] = useContext(AuthContext);
  const [page, setPage] = useState(1);
  const [total, setTotal] = useState(0);
  // state
  const [comments, setComments] = useState([]);
  const [loading, setLoading] = useState(false);
  const [keyword, setKeyword] = useState("");
  console.log("comments.length, total", comments.length, total);

  useEffect(() => {
    if (auth?.token) {
      fetchComments();
      getTotal();
    }
  }, [auth?.token]);

  useEffect(() => {
    if (page === 1) return;
    if (auth?.token) loadMore();
  }, [page, auth?.token]);

  const fetchComments = async () => {
    try {
      const { data } = await axios.get(`/comments/${page}`);
      setComments(data);
    } catch (err) {
      console.log(err);
    }
  };

  const getTotal = async () => {
    try {
      const { data } = await axios.get("/comment-count");
      setTotal(data);
    } catch (err) {
      console.log(err);
    }
  };

  const loadMore = async () => {
    try {
      const { data } = await axios.get(`/comments/${page}`);
      setComments([...comments, ...data]);
    } catch (err) {
      console.log(err);
    }
  };

  const handleDelete = async (comment) => {
    try {
      const answer = window.confirm("Are you sure you want to delete?");
      if (!answer) return;
      const { data } = await axios.delete(`/comment/${comment._id}`);
      console.log("DATATATA => ", data);
      if (data.ok) {
        setComments(comments.filter((c) => c._id !== comment._id));
        setTotal(total - 1);
        toast.success("Comment deleted successfully");
      }
    } catch (err) {
      console.log(err);
      toast.error("Delete failed. Try again.");
    }
  };

  const filteredComments =
    comments &&
    comments.filter((c) => c.content.toLowerCase().includes(keyword));

  return (
    <AdminLayout>
      <Row>
        <Col span={24}>
          <h1>{comments?.length} Comments</h1>

          <Input
            placeholder="Search"
            type="search"
            value={keyword}
            onChange={(e) => setKeyword(e.target.value.toLowerCase())}
          />

          <List
            style={{ marginTop: 20 }}
            itemLayout="horizontal"
            dataSource={filteredComments}
            renderItem={(item) => (
              <List.Item
                actions={[
                  <Link href={`/post/${item.postId.slug}#${item._id}`}>
                    <a>view</a>
                  </Link>,
                  <a onClick={() => handleDelete(item)}>delete</a>,
                ]}
              >
                <List.Item.Meta
                  description={`On ${item.postId.title} | ${
                    item.postedBy.name
                  } | ${dayjs(item.createdAt).format("L LT")}`}
                  title={item.content}
                />
              </List.Item>
            )}
          />
        </Col>
      </Row>

      <Row>
        <Col span={24} style={{ textAlign: "center" }}>
          {/* {comments?.length < total && ( */}
          {page * 3 < total && (
            <div style={{ padding: 50 }}>
              <Button
                size="large"
                type="primary"
                loading={loading}
                onClick={() => setPage(page + 1)}
              >
                Load More
              </Button>
            </div>
          )}
        </Col>
      </Row>
    </AdminLayout>
  );
};

export default Comments;