Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Azoruk's avatar

Duplicate results in endless pagination when new entries added to ajax query

On my website, users can post images. When a user browses images, if he scrolls down to the bottom of the page, new images (7 in total) will be fetched.

JS:

$(window).scroll(fetchImages);

function fetchImages() {

    var page = $('.endless-pagination').data('next-page');

    if(page !== null) {

        clearTimeout( $.data( this, "scrollCheck" ) );

        $.data( this, "scrollCheck", setTimeout(function() {
            var scroll_position_for_images_load = $(window).height() + $(window).scrollTop() + 900;

            if(scroll_position_for_images_load >= $(document).height()) {
                $.get(page, function(data){
                    var newPageUrl =  data.next_page + "&within={{ $within or '' }}&orderBy={{ $orderBy or '' }}";
                    $('.endless-pagination').append(data.images);
                    $('.endless-pagination').data('next-page', newPageUrl);
                    setResizeVariables();
                    updateReadMore();
                });     
                
            }
        }, 350))

    }
}       

Snippet from my controller in laravel:

$images = Status::where('is_image', 1)
            ->whereIn('is_mature', [0, $mature])
            ->where('created_at', '>=', Carbon::now()->subHours($withinHours))
            ->orderBy($orderByString, 'desc')
            ->Paginate(7);

if($request->ajax()) {
    return [
        'images' => view('browse.partials.fullview_partial')->with('current_time', $current_time)->with('images', $images->appends(request()->only('query')))->render(),
        'next_page' => $images->nextPageUrl()
    ];
}

and this is what my blade/html looks like:

@if ($images->count())
    <div class="endless-pagination" data-next-page="{{ $images->nextPageUrl() }}&query={{ $query or '' }}&within={{ $within or '' }}&orderBy={{ $orderBy or '' }}">
        @foreach ($images as $status)
            @include('templates.partials.status_block')
        @endforeach
    </div>
@endif

Notice that data-next-page here stores the next page number of pagination. So it would look something like this:

<div class="endless-pagination" data-next-page="http://localhost/browse/fullview?page=2&amp;query=&amp;within=alltime&amp;orderBy=date">

once the DOM is loaded. And then once this set of "pagination" is loaded (by scrolling to the bottom of the page), data-next-page would then change to show the next set of pagination.

And this all works just fine.

My problem, however, is that when a user posts a new image while another user scrolls through images, duplicate results will show.

So imagine User 1 and User 2.

User 1 is viewing images. User 2 is posting an image. Once user 1 gets to the bottom of the screen and loads new results, the last image he viewed will be loaded once again (and then 6 new ones because we paginate by 7). If User 2 posts two images before User 1 gets to the bottom of the page, there will be two duplicates, etc. In other words, User 2 is adding to User 1's query.

How could I prevent this from happening?

I was thinking of perhaps setting an initial $current_time timestamp once a user loads the page, and to only fetch results that were posted BEFORE that timestamp, but I wonder if there's a more elegant solution?

0 likes
2 replies
athulpraj's avatar
Level 1

You can fetch the next page by simply passing on the last image id and taking images having the id less than the id of the last image in the list. This way you can avoid the overlapping.

1 like
biishmar's avatar

@Azoruk

$images = Status::where('is_image', 1)
                ->whereIn('is_mature', [0, $mature])
                ->where('created_at', '>=', Carbon::now()->subHours($withinHours))
                

if($request->ajax()) {
    
    $images = $images->whereNotIn('previousDisplayedIds')
                     ->orderBy($orderByString, 'desc')
                     ->Paginate(7);
    
    $previousDisplayedIds = collect($images)->pluck('id');
    
    return [
        'images' => view('browse.partials.fullview_partial')->with('current_time', $current_time)->with('images', $images->appends(request()->only('query')))->render(),
        'next_page' => $images->nextPageUrl(),
        'previousDisplayedIds' => $previousDisplayedIds
    ];
}

$images = $images->orderBy($orderByString, 'desc')
                ->Paginate(7);

Save that previousdisplayedIds in javascript and send it on next request, so it wont overlap..

1 like

Please or to participate in this conversation.