Livewire Infinite Scrolling

In this Tutorial, we will implement Livewire Infinite Scrolling which will automatically Load Records from the Database as the User Scrolls to the bottom of the list. And unlike other examples that you might have seen, we will make sure our code is performant and do not fetch all the Records when the Component re-renders.

Livewire Infinite Scrolling

I am starting with Fresh Laravel Project with Livewire Installed. I have also installed Breeze, but that is not mandatory and entirely up to you. I have also set up a Route where we are going to display our Livewire Component. And finally, I have a Product Model which I have seeded with a few Records. We will be scrolling through the list of Products.

We can create the Livewire Component using the below Artisan Command.

php artisan make:livewire LoadProducts

We can render it in our View using the below command

@livewire('load-products')

And within this Livewire Component, I am going to define 2 properties, $page and $perPage. I am going to set these properties in the mount method.

<?php

namespace App\Http\Livewire;

use App\Models\Product;
use Livewire\Component;

class LoadProducts extends Component
{
    public $perPage;
    public $page;

    public function mount($page = null, $perPage = null) 
    {
        $this->page = $page ?? 1;
        $this->perPage = $perPage ?? 10;
    }
}

Since we didn’t provided any values while rendering the Livewire Component, $page and $perPage will have default value of 1 and 10 respectively.

Next we are going to create a render method which will return the records corresponding to 1st Page and pass it to our View load-products

    public function render()
    {
        $products = Product::paginate($this->perPage, ['*'], null, $this->page);
        return view('livewire.products.load-products', [
            'products' => $products
        ]);
    }

Within Our View File which is located at resources/views/livewire/products/load-products.blade.php we can simply loop through all the Products.

<div>
    @foreach($productsas $result)
       .
       . 
       .
    @endforeach
</div>

At this stage if you open up your route containing this Livewire Component, you will see first 10 records.

First of all we are going to use a Button to Load More Records. Once we get that working, we will switch to Scrolling.

Now we can add a button after our foreach loop ends like below

<x-button wire:click="loadMore">Load More Products</x-button>

At the click of this Button, we need to fetch the next 10 records and append to the current list. However, Livewire renders the whole Component so we can use the append approach.

Second option is to fetch all the 20 records and render the component again. This will give an illusion of Loading More Data but it has serious performance issues.

Instead what we are going to do is we are going to create another Component, LoadMoreProducts. This Component would be responsible for

  1. Displaying the above Load More Products Button.
  2. Handling the Click Event which will fetch the Products for 2nd and subsequent Pages.
  3. Conditionally displaying the View Related to LoadProducts Component.

So we can create this Component using below command:

php artisan make:livewire LoadMoreProducts

We will add this Component in our View File at resources/views/livewire/products/load-products.blade.php like below:

    @if($products->hasMorePages())
        @livewire('load-more-products', ['page' => $page, 'perPage' => $perPage, 'key' => 'products-page-' . $page])
    @endif

Please see that we are displaying this Component only if there are more Products to be fetched. We are also passing the $page and $perPage Property. We are also using a unique key to identify it as there are going to be multiple of these components, 1 for each subsequent page.

Within our Component File located at app/Http/Livewire/LoadMoreProducts, we will have the following code which at this stage is very similar to LoadProducts

<?php

namespace App\Http\Livewire;

use Livewire\Component;
use App\Models\Product;

class LoadMoreProducts extends Component
{

    public $perPage;
    public $page;
    public $loadMore = false;

    public function mount($page = null, $perPage = null) 
    {
        $this->page = $page ?? 1;
        $this->perPage = $perPage ?? 10;
    }

    public function render()
    {
        return view('livewire.products.load-more-products');
    }
}

Within the View File of this Component located at resources/views/livewire/products/load-more-products.blade.php, we will eventually display our Button.

<x-button wire:click="loadMore">Load More Products</x-button>

As you can see that we are calling a loadMore method at the click of this Button. We need to define this Method in LoadMoreProducts Component and it will handle following functionalities:

  1. We will increment the $page Property by 1.
  2. We will define a new Flag loadMore and set it to true.
  3. Within the render method we will check the above flag. If this Flag is true, we will fetch the Records from the DB for the given $page and display the View Related to LoadProducts Component. Otherwise, we will just display the View Related to LoadMoreProducts, which renders the Button.

Here is how our LoadMoreProducts Component looks like

<?php

namespace App\Http\Livewire;

use Livewire\Component;
use App\Models\Product;

class LoadMoreProducts extends Component
{

    public $perPage;
    public $page;
    public $loadMore = false;

    public function mount($page = null, $perPage = null) 
    {
        $this->page = $page ?? 1;
        $this->perPage = $perPage ?? 10;
    }

    public function loadMore() 
    {
        $this->page += 1;
        $this->loadMore = true;
    }

    public function render()
    {
        if (!$this->loadMore) {
            return view('livewire.products.load-more-products');
        } else {
            $products = Product::paginate($this->perPage, ['*'], null, $this->page);

            return view('livewire.products.load-products', [
                'products' => $products
            ]);
        }
    }
}

So when the User asks for the 2nd Page, we are fetching the Products related to 2nd Page and displaying them via livewire.products.load-products. This View is then also displaying the “Load More Products” Button for the 3rd Page.

This way by breaking the functionality to two components we have tackled the issue of appending to the current list.

Now let us make changes to load more Products as the User Scrolls rather than the Click of a Button. We only need to make changes in the View of LoadMoreProducts. We need to fire the click event as the user Scrolls to the end of the List. We can do so by using x-init Directive of the AlpineJs.

<div x-data="{
    checkScroll() {
            window.onscroll = function(ev) {
                if ((window.innerHeight + window.scrollY) >= document.body.scrollHeight) {
                     @this.call('loadMore')
                }
            };
        }
    }"

    x-init="checkScroll"
>

Now as the User Scrolls to the end of the List, loadMore Method will be triggered and Records would be appended to the end of the list. We will get the same output as shown in the image at the start of the Tutorial.

If you are interested in the code, you can get it from Github.

If you are interested in watching the Video Version of this Article, you can view it at our YouTube Channel.

Leave a Reply

Your email address will not be published. Required fields are marked *