Home > Blockchain >  Resume pagination after specific document
Resume pagination after specific document

Time:08-27

I'm loading user comments on my website. Initially, I only load a subset of comments and show a Load more button which loads the next page and appends it to the end of the comments list.

enter image description here

Loading another page works by using the usual skip and limit aggregation steps in MongoDB:

const aggregationResult = await ResourceCommentModel.aggregate()
    .match({
        resourceId: new mongoose.Types.ObjectId(req.params.resourceId),
        parentCommentId: undefined
    })
    .sort({ 'createdAt': -1 })
    .facet({
        comments: [
            { $skip: (page - 1) * pageSize },
            { $limit: pageSize },
            {
                $lookup: {
                    from: 'resourcecomments',
                    localField: '_id',
                    foreignField: 'parentCommentId',
                    as: 'replies',
                }
            }
        ],
        totalCount: [{ $group: { _id: null, count: { $sum: 1 } } }]
    })
    .exec();

Problem: If new comments have been posted in the meantime, comments from page one have been pushed to page 2, and we are loading duplicate comments (and showing them in the UI).

enter image description here

One option would be to not rely on the page number, but instead start after the last loaded timestamp:

comments: [
    { $match: { createdAt: { $gt: new Date(continueAfterTimestamp) } } },
    { $limit: pageSize },
    {
        $lookup: {
            from: 'resourcecomments',
            localField: '_id',
            foreignField: 'parentCommentId',
            as: 'replies',
        }
    }
],

However, there are 2 problems with this approach:

  1. Although unlikely, if two comments were posted at the exact same millisecond, one of them will not be loaded.

  2. The bigger problem: I'm planning to later allow sorting comments by upvotes, and then I don't have a way to start after the last loaded document anymore.

CodePudding user response:

Use ObjectID instead of the timestamp. It is unique, and has a timestamp inside.

{ $match: { _id: { $gt: new ObjectId(continueAfterID) } } },

For the "bigger problem" add upvotes to the play. You gonna need an index anyway, so make it compound {upvote:-1, _id:1} and pass the latest upvote to the filter:

{ $match: { $or:[
    {$and: [{upvote: continueUpvote}, {_id: { $gt: new ObjectId(continueAfterID) } }] },
    {upvote: {$lt: continueUpvote} }
]}}

Or just dedup results clientside. It's trivial with react, and still easy to do with vanilla js and real DOM.

  • Related