Laravel.io
//routes/web.php
 
Route::get('/items', function() {
    return view('items');
});

//resources/views/items.blade.php
@extends('layouts.app')

@section('content')
    <h1 class="text-3xl font-extrabold text-gray-900 tracking-tight">Items</h1>

    <livewire:load-items perPage="10" page="1" />

@endsection

@push('scripts')
<script>
var latestComponentId = '';
var isLatestComponentLoaded = false;
Livewire.hook('component.initialized', component => {
    //
    if('load-more-items' == component.fingerprint.name) {
        latestComponentId = component.id;
        isLatestComponentLoaded = true;
    }
})

window.onscroll = function(ev) {
    //document.body.scrollHeight worked for me insted of document.body.offsetHeight
    if ((window.innerHeight + window.scrollY) >= document.body.scrollHeight && isLatestComponentLoaded) {
        isLatestComponentLoaded = false;
        window.livewire.find(latestComponentId).call('loadMoreItems');
    }
};
</script>
@endpush

//App\Http\Livewire\LoadItems.php
<?php

namespace App\Http\Livewire;

use Livewire\Component;
use App\Models\Item;

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

    public function mount($page, $perPage) 
    {
        $this->page = $page ?? 1;
        $this->perPage = $perPage ?? 10;
    }
    public function render()
    {
        $results = Item::paginate($this->perPage, ['*'], null, $this->page);
        return view('livewire.load-items', [
            'results' => $results
        ]);
    }
}

//App\Http\Livewire\LoadMoreItems.php
<?php

namespace App\Http\Livewire;

use Livewire\Component;
use App\Models\Item;

class LoadMoreItems extends Component
{
    public $page;
    public $perPage;
    private $loadMore = false;

    protected $listeners = ['loadMoreItems'];

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

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

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

//resources/views/livewire/load-items.blade.php
<div>
    <p class="font-bold">Time is {{ time() }}</p>
    <ul>    
    @foreach($results as $result)
        <li class="mt-4 border-b-2 p-4">{{$result->id}}: {{$result->name}}</li}>
    @endforeach
    </ul>

    @if($results->hasMorePages())
        <livewire:load-more-items :page="$page" :perPage="$perPage" :key="'load-more-items-'.$page" />
    @endif
</div>

//resources/views/livewire/load-more-items.blade.php
<div>
    <div wire:loading.delay>
        <svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-red-700" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
        <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
        <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
        </svg>
    </div>
</div>

Please note that all pasted data is publicly available.