Pagination
Display large datasets across multiple pages with automatic URL synchronization and reactive navigation.
Overview
Pagination in Diffyne is as easy as Livewire. Use the HasPagination trait to add pagination functionality to any component. It automatically handles:
- Page navigation - Next, previous, and direct page links
- URL synchronization - Page number in URL query string
- State management - Automatic serialization of paginator objects
- Reactive updates - UI updates automatically when page changes
Quick Start
1. Use the Trait
<?php
namespace App\Diffyne;
use Diffyne\Component;
use Diffyne\Traits\HasPagination;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
class UserList extends Component
{
use HasPagination;
public ?LengthAwarePaginator $users = null;
public function mount(): void
{
$this->loadUsers();
}
protected function onPageChange(): void
{
$this->loadUsers();
}
protected function loadUsers(): void
{
$this->users = User::query()
->orderBy('name')
->paginate($this->perPage, ['*'], 'page', $this->page);
}
public function render()
{
return view('diffyne.user-list');
}
}2. Display Pagination Links
<div>
{{-- Your data table --}}
<table>
@foreach($users as $user)
<tr>
<td>{{ $user->name }}</td>
<td>{{ $user->email }}</td>
</tr>
@endforeach
</table>
{{-- Pagination links --}}
@if($users && $users->hasPages())
<div class="mt-6">
{{ $users->links('diffyne::pagination') }}
</div>
@endif
</div>That's it! The pagination is fully functional and reactive.
How It Works
Automatic Properties
The HasPagination trait provides these properties:
$page- Current page number (1-indexed, synced with URL)$perPage- Items per page (default: 15)
Automatic Methods
nextPage()- Go to next pagepreviousPage()- Go to previous pagegoToPage($page)- Jump to specific pageresetPage()- Go back to page 1
Lifecycle Hook
Implement onPageChange() to reload data when page changes:
protected function onPageChange(): void
{
$this->loadUsers();
}Basic Example
Component
<?php
namespace App\Diffyne;
use App\Models\Post;
use Diffyne\Component;
use Diffyne\Traits\HasPagination;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
class PostList extends Component
{
use HasPagination;
public ?LengthAwarePaginator $posts = null;
public function mount(): void
{
$this->loadPosts();
}
protected function onPageChange(): void
{
$this->loadPosts();
}
protected function loadPosts(): void
{
$this->posts = Post::query()
->latest()
->paginate($this->perPage, ['*'], 'page', $this->page);
}
public function render()
{
return view('diffyne.post-list');
}
}View
<div>
<h2>Posts</h2>
@if($posts && $posts->count() > 0)
<div class="space-y-4">
@foreach($posts as $post)
<article class="border p-4">
<h3>{{ $post->title }}</h3>
<p>{{ $post->excerpt }}</p>
</article>
@endforeach
</div>
{{-- Pagination --}}
@if($posts->hasPages())
<div class="mt-6">
{{ $posts->links('diffyne::pagination') }}
</div>
@endif
@else
<p>No posts found.</p>
@endif
</div>Customizing Items Per Page
Change Default
class PostList extends Component
{
use HasPagination;
public int $perPage = 25; // Default is 15
// ...
}Allow User Selection
use Diffyne\Attributes\Invokable;
#[Invokable]
public function setPerPage(int $perPage): void
{
$this->perPage = $perPage;
$this->resetPage(); // Go back to page 1
$this->onPageChange();
}<select diff:model.live="perPage">
<option value="10">10 per page</option>
<option value="25">25 per page</option>
<option value="50">50 per page</option>
</select>Combining with Search
When search changes, reset to page 1:
use Diffyne\Attributes\QueryString;
#[QueryString(keep: true)]
public ?string $search = '';
public function updated(string $property): void
{
parent::updated($property);
if ($property === 'search') {
$this->resetPage();
$this->loadUsers();
}
}
protected function loadUsers(): void
{
$query = User::query();
if (!empty($this->search)) {
$query->where('name', 'like', '%'.$this->search.'%');
}
$this->users = $query
->orderBy('name')
->paginate($this->perPage, ['*'], 'page', $this->page);
}Pagination Helper Methods
The LengthAwarePaginator provides useful methods:
{{-- Check if pagination is needed --}}
@if($users->hasPages())
{{ $users->links('diffyne::pagination') }}
@endif
{{-- Display info --}}
<p>
Showing {{ $users->firstItem() }} to {{ $users->lastItem() }}
of {{ $users->total() }} results
</p>
{{-- Check page position --}}
@if($users->onFirstPage())
<p>You're on the first page</p>
@endif
@if($users->hasMorePages())
<p>More pages available</p>
@endifURL Synchronization
The $page property is automatically synced with the URL query string:
- Page 1:
/users - Page 2:
/users?page=2 - Page 3:
/users?page=3
Users can:
- Bookmark specific pages
- Share links to specific pages
- Use browser back/forward buttons
- Refresh the page and stay on the same page
Complete Example: Search with Pagination
Component
<?php
namespace App\Diffyne;
use App\Models\User;
use Diffyne\Attributes\Invokable;
use Diffyne\Attributes\QueryString;
use Diffyne\Component;
use Diffyne\Traits\HasPagination;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
class UserList extends Component
{
use HasPagination;
public ?LengthAwarePaginator $users = null;
#[QueryString(keep: true)]
public ?string $search = '';
public bool $loading = false;
public function mount(): void
{
$this->loadUsers();
}
public function updated(string $property): void
{
parent::updated($property);
if ($property === 'search') {
$this->resetPage();
$this->loadUsers();
}
}
#[Invokable]
public function clearSearch(): void
{
$this->search = '';
$this->resetPage();
$this->loadUsers();
}
protected function onPageChange(): void
{
$this->loadUsers();
}
protected function loadUsers(): void
{
$query = User::query();
if (!empty($this->search)) {
$query->where(function ($q) {
$q->where('name', 'like', '%'.$this->search.'%')
->orWhere('email', 'like', '%'.$this->search.'%');
});
}
$this->users = $query
->orderBy('name')
->paginate($this->perPage, ['*'], 'page', $this->page);
}
public function render()
{
return view('diffyne.user-list');
}
}View
<div class="user-list-container">
{{-- Search --}}
<div class="mb-4">
<input
type="text"
diff:model.live.debounce.300="search"
placeholder="Search users..."
class="w-full px-4 py-2 border rounded-lg"
>
</div>
{{-- Results count --}}
@if($users)
<p class="text-sm text-gray-600 mb-4">
Showing {{ $users->firstItem() }} to {{ $users->lastItem() }}
of {{ $users->total() }} users
@if(!empty($search))
matching "{{ $search }}"
@endif
</p>
@endif
{{-- Users table --}}
@if(!$users || $users->isEmpty())
<p class="text-center py-8 text-gray-500">
@if(empty($search))
No users found.
@else
No users match "{{ $search }}"
@endif
</p>
@else
<table class="w-full">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
</tr>
</thead>
<tbody>
@foreach($users as $user)
<tr diff:key="{{ $user->id }}">
<td>{{ $user->name }}</td>
<td>{{ $user->email }}</td>
</tr>
@endforeach
</tbody>
</table>
@endif
{{-- Pagination --}}
@if($users && $users->hasPages())
<div class="mt-6">
{{ $users->links('diffyne::pagination') }}
</div>
@endif
</div>Best Practices
1. Always Reset Page on Filter Changes
public function updated(string $property): void
{
if (in_array($property, ['search', 'category', 'status'])) {
$this->resetPage(); // Important!
$this->loadData();
}
}2. Use onPageChange() Hook
Don't call loadData() directly from navigation methods:
// ✅ Good
protected function onPageChange(): void
{
$this->loadData();
}
// ❌ Avoid
#[Invokable]
public function nextPage(): void
{
$this->page++;
$this->loadData(); // Don't do this
}3. Check for Empty Results
@if($users && $users->count() > 0)
{{-- Display data --}}
@else
<p>No results found.</p>
@endif4. Use diff:key for List Items
@foreach($users as $user)
<tr diff:key="{{ $user->id }}">
{{-- Row content --}}
</tr>
@endforeachThis ensures efficient DOM updates when paginating.
5. Show Loading States
<div diff:loading class="opacity-50">
{{-- Content --}}
</div>Common Patterns
Filtering with Pagination
public ?string $status = 'all';
public ?string $category = null;
public function updated(string $property): void
{
if (in_array($property, ['status', 'category'])) {
$this->resetPage();
$this->loadPosts();
}
}
protected function loadPosts(): void
{
$query = Post::query();
if ($this->status !== 'all') {
$query->where('status', $this->status);
}
if ($this->category) {
$query->where('category_id', $this->category);
}
$this->posts = $query
->latest()
->paginate($this->perPage, ['*'], 'page', $this->page);
}Sorting with Pagination
use Diffyne\Attributes\Invokable;
public string $sortBy = 'created_at';
public string $sortDir = 'desc';
#[Invokable]
public function sort(string $field): void
{
if ($this->sortBy === $field) {
$this->sortDir = $this->sortDir === 'asc' ? 'desc' : 'asc';
} else {
$this->sortBy = $field;
$this->sortDir = 'asc';
}
$this->resetPage();
$this->loadData();
}
protected function loadData(): void
{
$this->items = Item::query()
->orderBy($this->sortBy, $this->sortDir)
->paginate($this->perPage, ['*'], 'page', $this->page);
}Troubleshooting
Pagination Not Updating
Problem: Clicking pagination links doesn't change the page.
Solution: Make sure you've implemented onPageChange():
protected function onPageChange(): void
{
$this->loadData(); // Reload data when page changes
}Page Resets on Every Request
Problem: Always goes back to page 1.
Solution: Don't reset $page in mount() or updated() unless necessary:
// ❌ Wrong
public function mount(): void
{
$this->page = 1; // Don't do this
$this->loadData();
}
// ✅ Correct
public function mount(): void
{
$this->loadData(); // Page is already 1 by default
}URL Not Updating
Problem: Page number not in URL.
Solution: The $page property is automatically synced with URL via #[QueryString]. If it's not working, check that you're using the trait correctly.
Empty Results After Pagination
Problem: No data shown after changing pages.
Solution: Ensure onPageChange() calls your data loading method:
protected function onPageChange(): void
{
$this->loadUsers(); // Make sure this method exists and works
}Next Steps
- Query String Binding - Learn about URL synchronization
- Data Binding - Two-way data binding
- Search Example - Complete search implementation
- Component Events - Event handling
