The issue you're experiencing with pagination in your Livewire component is likely due to the state of the pagination not being properly reset when navigating between pages. This can happen if the pagination state is not correctly managed or if there are issues with the Livewire component lifecycle.
Here are a few steps to troubleshoot and potentially resolve the issue:
-
Ensure Pagination State is Reset Correctly: Make sure that the pagination state is reset whenever necessary, especially when filters or sorting are applied. You are already using
resetPage()in some methods, but ensure it's used consistently. -
Check for Livewire Pagination Issues: Sometimes, issues can arise from how Livewire handles pagination internally. Ensure you are using the latest version of Livewire, as bugs are often fixed in newer releases.
-
Debugging Pagination: Add some debugging statements to check the current page and the items being fetched. This can help identify if the issue is with the data being fetched or with the pagination state.
-
Simplify the Component: Try to simplify the component to isolate the issue. Remove unnecessary methods and see if the problem persists. This can help identify if a specific part of the component is causing the issue.
Here is a revised version of your Livewire component with some additional debugging and state management improvements:
namespace App\Livewire\Invoices;
use App\Models\Invoice;
use App\Models\Supplier;
use App\Models\User;
use App\Models\AttestRule;
use Auth;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\Notification;
use Jantinnerezo\LivewireAlert\LivewireAlert;
use Livewire\Attributes\On;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
class All extends Component
{
use LivewireAlert;
use WithPagination;
public array $statuses = ['waiting', 'pending', 'attested', 'paid', 'deleted'];
public array $suppliers = [];
public array $attestRules = [];
public array $users = [];
public array $attestors = [];
public bool $archived = false;
public int $countArchived = 0;
#[Url]
public string $sortColumn = 'invoices.name'; // Default col to sort
#[Url]
public string $sortDirection = 'asc'; // Default sort order
public array $searchColumns = [
'name' => '',
'amount' => '',
'supplier_id' => 0,
'attest_rule_id' => 0,
'attestor_id' => 0,
'status' => ''
];
public array $selected = [];
public function mount(): void
{
$this->suppliers = Supplier::pluck('name', 'id')->toArray();
$this->attestors = User::where('users.team_id', '=', Auth::user()->team_id)->pluck('name', 'id')->toArray();
$this->attestRules = AttestRule::where('parent_id', null)->pluck('name', 'id')->toArray();
$this->users = User::where('users.team_id', '=', Auth::user()->team_id)->pluck('name', 'id')->toArray();
$this->countArchived = Invoice::onlyTrashed()->count();
}
public function updating($key): void
{
if (in_array($key, ['searchColumns.name', 'searchColumns.attestor_id', 'searchColumns.supplier_id', 'searchColumns.attest_rule_id', 'searchColumns.amount'])) {
$this->resetPage(); // Apply the typed in search immediately
}
}
public function getSelectedCountProperty(): int
{
return count($this->selected);
}
public function deleteConfirm($method, $id = null): void
{
$this->dispatch('swal:confirm', [
'type' => 'warning',
'title' => 'Are you sure?',
'text' => '',
'id' => $id,
'method' => $method,
]);
}
#[On('deleteSelected')]
public function deleteSelected(): void
{
$invoices = Invoice::whereIn('id', $this->selected)->get();
$invoices->each->delete();
$this->reset('selected');
$this->countArchived = Invoice::onlyTrashed()->count();
}
#[On('delete')]
public function delete(int $id): void
{
$invoice = Invoice::where('id', $id)->firstOrFail();
$invoice->delete();
$this->countArchived = Invoice::onlyTrashed()->count();
}
public function clearFiltersAndSorts()
{
$this->reset(['searchColumns', 'sortColumn', 'sortDirection']);
}
public function toggleArchived()
{
$this->archived = !$this->archived;
$this->resetPage();
}
public function recover($invoice)
{
$invoice = Invoice::onlyTrashed()->where('id', '=', $invoice)->firstOrFail();
$invoice->deleted_at = null;
$this->archived = false;
$invoice->update();
$this->countArchived = Invoice::onlyTrashed()->count();
$this->resetPage();
}
public function sortByColumn(string $column): void
{
if ($this->sortColumn === $column) {
$this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc';
} else {
$this->reset('sortDirection');
$this->sortColumn = $column;
}
$this->resetPage();
}
public function editItem($url)
{
return $this->redirect($url);
}
public function halt($invoice_id)
{
if (!Auth::user()->manage_invoice) {
abort(404);
}
$invoice = Invoice::findOrFail($invoice_id);
$invoice->halter_id = Auth::id();
$invoice->update();
$payers = User::where('manage_payment', true)->get();
foreach ($payers as $payer) {
Notification::route('mail', $payer->email)->notify(new SendHaltNotification());
}
$this->resetPage();
$this->alert('success', 'Invoice halted, feel free to leave a comment about your action.', ['timer' => 8000]);
}
public function togglePaid($invoice_id)
{
$invoice = Invoice::findOrFail($invoice_id);
if (!Auth::user()->manage_payment || !in_array($invoice->status, ['paid', 'attested'])) {
abort(404);
}
if ($invoice->status === 'paid') {
$invoice->status = 'attested';
$invoice->payer_id = null;
$invoice->update();
$this->alert('success', 'Invoice status changed to "attested"');
} else {
$invoice->status = 'paid';
$invoice->payer_id = Auth::id();
$invoice->update();
$this->alert('success', 'Invoice status changed to "paid"');
}
$this->resetPage();
}
public function accept($invoice_id)
{
$invoice = Invoice::with('attestRule.blocks.attestors')->findOrFail($invoice_id);
$invoice->halter_id = null;
$invoice->update();
// Send email to any pending attestor or payer
$emails = [];
if (!empty($invoice->attestRule->blocks)) {
foreach ($invoice->attestRule->blocks as $block) {
foreach ($block->attestors as $attestor) {
if ($attestor->pivot->status == 'pending') {
$emails[] = $attestor->email;
}
}
}
}
if (!empty($emails)) { // Send email to attestors
foreach ($emails as $email) {
Notification::route('mail', $email)->notify(new SendAttestNotification());
}
} else { // Send email to payers
$payers = User::where('manage_payment', true)->get();
foreach ($payers as $payer) {
Notification::route('mail', $payer->email)->notify(new SendPayNotification());
}
}
$this->resetPage();
$this->alert('success', 'Invoice reactivated, pending attestors or payers will be emailed, also feel free to leave a comment about your action.', ['timer' => 8000]);
}
public function render(): View
{
if ($this->archived) {
$invoices = Invoice::query()
->select([
'invoices.*', 'suppliers.id as supplierId', 'suppliers.name as supplierName', 'attest_rules.id as attestRuleId', 'attest_rules.name as attestRuleName',
'users.name as userName'
])
->leftJoin('suppliers', 'suppliers.id', '=', 'invoices.supplier_id')
->leftJoin('attest_rules', 'attest_rules.id', '=', 'invoices.attest_rule_id')
->leftJoin('users', 'users.id', '=', 'invoices.ub')
->with('attestRule.blocks.attestors')
->withCount('comments')
->onlyTrashed();
} else {
$invoices = Invoice::query()
->select([
'invoices.*', 'suppliers.id as supplierId', 'suppliers.name as supplierName', 'attest_rules.id as attestRuleId', 'attest_rules.name as attestRuleName',
'users.name as userName'
])
->leftJoin('suppliers', 'suppliers.id', '=', 'invoices.supplier_id')
->leftJoin('attest_rules', 'attest_rules.id', '=', 'invoices.attest_rule_id')
->leftJoin('users', 'users.id', '=', 'invoices.ub')
->with('attestRule.blocks.attestors')
->withCount('comments');
}
foreach ($this->searchColumns as $column => $value) {
if (!empty($value)) {
$invoices
->when($column === 'name', fn($invoices) => $invoices->where('invoices.'.$column, 'LIKE', '%'.$value.'%'))
->when($column === 'attest_rule_id', fn($invoices) => $invoices->whereRelation('attestRule', 'attest_rules.id', $value))
->when($column === 'due_date', fn($invoices) => $invoices->where('invoices.'.$column, 'LIKE', '%'.$value.'%'))
->when($column === 'amount', fn($invoices) => $invoices->where('invoices.'.$column, 'LIKE', '%'.$value.'%'))
->when($column === 'supplier_id', fn($invoices) => $invoices->whereRelation('supplier', 'suppliers.id', $value))
->when($column === 'ub', fn($invoices) => $invoices->whereRelation('user', 'users.id', $value))
->when($column === 'attestor_id', fn($invoices) => $invoices->whereRelation('user', 'users.id', $value))
->when($column === 'status', fn($invoices) => $invoices->where('invoices.'.$column, 'LIKE', '%'.$value.'%'));
}
}
$invoices->orderBy($this->sortColumn, $this->sortDirection);
return view('livewire.invoices.all', [
'invoices' => $invoices->simplePaginate(10)
]);
}
}
Blade Template Adjustments
Ensure that the pagination links are correctly rendered in your Blade template. You can use the links() method provided by Laravel's paginator:
<div>
<div class="py-6">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
<div class="overflow-hidden bg-white shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<x-section-header>Invoices All</x-section-header>
<div class="mb-2">
<div class="mb-4 py-1 flex">
@if($countArchived > 0)
<x-button wire:click="toggleArchived" class="ml-2" :second="!$archived">
@if($archived)
Show active
@else
Show archived
@endif
</x-button>
@endif
</div>
</div>
<div class="overflow-hidden overflow-x-auto mb-4 min-w-full align-middle sm:rounded-md">
<div class="relative">
<table class="min-w-full border divide-y divide-gray-200">
<thead>
<!-- Table headers -->
</thead>
<tbody class="bg-white divide-y divide-gray-200 divide-solid">
@forelse($invoices as $invoice)
<!-- Table rows -->
@empty
<tr class="bg-gray-300">
<td colspan="9" class="text-center">No results found!</td>
</tr>
@endforelse
</tbody>
</table>
@if(App::environment('local'))
<div wire:loading class="absolute inset-0 bg-white opacity-50"></div>
<div wire:loading.delay>
<div wire:loading.flex class="flex justify-center items-center absolute inset-0">
<x-icon-spinner/>
</div>
</div>
@endif
</div>
<div class="mt-5">
{{ $invoices->links() }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
Conclusion
By ensuring that the pagination state is correctly managed and that the pagination links are properly rendered, you should be able to resolve the issue with the pagination displaying data "one step ahead." If the problem persists, consider simplifying the component further to isolate the issue or consult the Livewire documentation for additional troubleshooting tips.