🚀 Building a Scalable User Grid in Laravel Livewire (Infinite Scroll + Filters)
Modern web apps demand fast, responsive, and interactive UIs — especially when dealing with large datasets like users, products, or CRM records.
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






