Sitemap

🚀 Building a Scalable User Grid in Laravel Livewire (Infinite Scroll + Filters)

3 min readMay 14, 2026

--

Modern web apps demand fast, responsive, and interactive UIs — especially when dealing with large datasets like users, products, or CRM records.

Press enter or click to view image in full size

In this article, we’ll build a scalable User Grid in Laravel Livewire featuring:

  • ⚡ Infinite scroll pagination
  • 🔍 Live search filtering
  • 🎛 Status filtering (Active / Inactive)
  • 🧩 Component-based architecture
  • 🚀 Optimized for large datasets

🧠 Architecture Overview

Instead of traditional pagination, we’ll use a chunk-based rendering system:

  • UsersGrid → Parent component (state + filters)
  • UsersPage → Child component (data chunks)
  • Alpine.js → Infinite scroll trigger

This gives us:

  • better UI performance
  • modular structure
  • scalable data loading

📦 Step 1: Create Livewire Components

php artisan make:livewire UsersGrid --class
php artisan make:livewire UsersPage --class

⚙️ Step 2: Parent Component (UsersGrid)

Get Shibon’s stories in your inbox

Join Medium for free to get updates from this writer.

This component manages:

  • filters
  • pagination chunks
  • infinite scroll state
use App\Models\User;
use Livewire\Component;

class UsersGrid extends Component
{
public $pages = 1;
public $perPage = 10;

public $search = '';
public $status = null;

public $hasMore = true;
public $loading = false;

public function updatedSearch()
{
$this->resetGrid();
}

public function updatedStatus()
{
$this->resetGrid();
}

public function resetGrid()
{
$this->pages = 1;
$this->hasMore = true;
}

public function loadMore()
{
if ($this->loading || ! $this->hasMore) return;

$this->loading = true;

$this->pages++;

$total = $this->query()->count();

if ($this->pages * $this->perPage >= $total) {
$this->hasMore = false;
}

$this->loading = false;
}

public function query()
{
return User::query()
->when($this->search, fn ($q) =>
$q->where('name', 'like', "%{$this->search}%")
->orWhere('email', 'like', "%{$this->search}%")
)
->when($this->status !== null, fn ($q) =>
$q->where('status', $this->status)
)
->latest();
}

public function render()
{
return view('livewire.users-grid');
}
}

🎨 Step 3: UI with Filters + Infinite Scroll

<div class="space-y-4">

<!-- Filters -->
<div class="flex gap-3 p-4 bg-white rounded shadow">

<input
type="text"
wire:model.live="search"
placeholder="Search users..."
class="border px-3 py-2 rounded w-1/2"
/>

<select wire:model.live="status" class="border px-3 py-2 rounded">
<option value="">All</option>
<option value="1">Active</option>
<option value="0">Inactive</option>
</select>

</div>

<!-- Data Pages -->
@for ($page = 1; $page <= $pages; $page++)
<livewire:users-page
:page="$page"
:perPage="$perPage"
:search="$search"
:status="$status"
:key="'users-page-'.$page.'-'.$search.'-'.$status"
/>
@endfor

<!-- Infinite Scroll Trigger -->
@if($hasMore)
<div
x-data
x-intersect="$wire.loadMore()"
class="py-6 text-center text-gray-400"
>
<span wire:loading wire:target="loadMore">
Loading more users...
</span>

<span wire:loading.remove wire:target="loadMore">
Scroll down to load more ↓
</span>
</div>
@else
<div class="text-center text-gray-500 py-6">
🎉 No more users to load
</div>
@endif

</div>

👤 Step 4: Child Component (UsersPage)

Each page loads only its own chunk of data.

use App\Models\User;
use Livewire\Component;

class UsersPage extends Component
{
public $page;
public $perPage;
public $search;
public $status;

public function mount($page, $perPage, $search, $status)
{
$this->page = $page;
$this->perPage = $perPage;
$this->search = $search;
$this->status = $status;
}

public function render()
{
$query = User::query()
->when($this->search, fn ($q) =>
$q->where('name', 'like', "%{$this->search}%")
->orWhere('email', 'like', "%{$this->search}%")
)
->when($this->status !== null && $this->status !== '', fn ($q) =>
$q->where('status', $this->status)
)
->latest();

return view('livewire.users-page', [
'users' => $query
->skip(($this->page - 1) * $this->perPage)
->take($this->perPage)
->get()
]);
}
}

🧩 Step 5: Child View

<div class="space-y-2">

@foreach($users as $user)
<div class="p-3 bg-white border rounded shadow-sm">
<div class="font-semibold">{{ $user->name }}</div>
<div class="text-sm text-gray-500">{{ $user->email }}</div>
</div>
@endforeach

</div>

⚡ What Makes This Powerful?

1. Infinite Scroll UX

2. Filter Reactivity

3. Component Isolation

4. Scalable Architecture

--

--

Shibon
Shibon

Written by Shibon

Full Stack Developer | Laravel | Symfony | Vue | React | MySQL | MongoDB | RabbitMQ | Elastic Search | Solr | Php web developer