Livewire Infinite Scrolling
Tags: laravel, livewire, alpinejs, php
Published: 2021-06-03 11:52:34

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. 

![Preview](https://media.giphy.com/media/tJWJHww3TNf8qENoPT/giphy.gif)

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](https://bit.ly/3w7M8Hn).

Hope you have enjoyed this tutorial. For similar articles, you can follow me on [Twitter](https://twitter.com/TheLaravelDev)