Laravel – 8.6.0 (Jetstream), Livewire – 2.2.7
Among other stuff, I have two simple tables with search input rendered with Livewire. Both Livewire components have almost the same logic, the main difference is DB query format. Everything is working perfectly in one table (search, pagination), but in the second table, there’s a problem with pagination.
What’s wrong? When I click on the pagination link (eg. from first-page go to page(2)), the request is sent, the URL in the browser changes to a proper query string, a response is received, but there’s no HTML part in the response, there’s no change in the DOM and pagination shows the first page is active. If I manually refresh the page, the table shows data for page 2, and after that, I can even get new data for the next or previous page, but only once after the initial page refresh. If I click on pagination links again, no HTML data in response, no DOM change …
What have I tried?
- I’ve gone through the Livewire documentation, tried all suggested solutions from the “troubleshooting” section.
- I’ve checked my code multiple times, compared both Livewire components and Blade views, haven’t been able to find some obvious mistake.
- For the past 10 days, I’ve been searching for the answer on SO, Livewire forum, Laracast forum, … found some good suggestions, tried most of them, but none of them solves my issue.
- Eliminated the possibility that logic for DB query causes some issues, problem exits even if I don’t use complex record filtering (eg. if I just paginate all records – Model::all()->paginate(10)).
- Problematic table has some additional record filtering via radio buttons, I’ve temporarily removed that, but the problem remains.
Code for the working Livewire component and Blade view:
<?php namespace AppModels; use IlluminateDatabaseEloquentModel; use IlluminateDatabaseEloquentFactoriesHasFactory; class Extension extends Model { use HasFactory; protected $guarded = []; public function user() { return $this->belongsTo('AppModelsUser', 'directory', 'ext'); } public static function search($query) { return empty($query) ? static::query() : static::where('directory', 'like', '%' . $query . '%') ->orWhere('mobile', 'like', '%' . $query . '%') ->orWhere('department', 'ilike', '%' . $query . '%') ->orWhere('name1', 'ilike', '%' . $query . '%') ->orWhere('name2', 'ilike', '%' . $query . '%'); } }
<?php namespace AppHttpLivewire; use LivewireComponent; use LivewireWithPagination; class PhonebookTable extends Component { use WithPagination; public $perPage = 10; public $search = ''; public function render() { return view('phonebook.phonebook-table', [ 'phonebook' => AppModelsExtension::search($this->search) ->with('user') ->orderBy('directory', 'asc') ->paginate(10) ]); } } /** * Livewire Lifecycle Hook */ public function updatingSearch(): void { $this->resetPage(); } }
<div> <div class="flex items-center justify-between"> {{-- Search Input --}} <div class="relative text-gray-700 focus-within:text-gray-600 m-2"> <span class="absolute inset-y-0 left-0 flex items-center pl-2"> <button type="submit" class="p-1 focus:outline-none focus:shadow-outline"> <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="w-6 h-6"> <path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path> </svg> </button> </span> <input wire:model="search" type="search" name="q" class="py-2 text-sm text-gray bg-gray-100 rounded-md pl-10 focus:outline-none focus:bg-gray-200 focus:text-gray-700" placeholder="{{ __('Search...') }}" autocomplete="off"> </div> {{-- End Of Search Input --}} {{-- PerPage Select Dropdown --}} <div> <span>{{ __('Per page:') }}</span> <div class="inline-block relative mr-2"> <select wire:model="perPage" class="block appearance-none w-full bg-white border border-gray-400 hover:border-gray-500 px-4 py-2 pr-8 rounded shadow leading-tight focus:outline-none focus:shadow-outline"> <option>10</option> <option>25</option> <option>50</option> </select> <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700"> <svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"> <path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z" /> </svg> </div> </div> </div> {{-- End Of PerPage Select Dropdown --}} </div> {{-- Phonebook Table --}} <div class="container p-2"> <table class="text-center w-full"> <thead class="bg-gray-400 flex text-white w-full item-center rounded-md"> <tr class="flex w-full py-2"> <th class="px-4 w-1/4">{{ __('Name1') }}</th> <th class="px-4 w-1/4">{{ __('Name2') }}</th> <th class="px-4 w-1/4">{{ __('Department') }}</th> <th class="px-4 w-1/4">{{ __('Extension') }}</th> <th class="px-4 w-1/4">{{ __('Mobile') }}</th> </tr> </thead> <tbody class=""> @foreach ($phonebook as $directory) <tr class="flex w-full py-2 hover:bg-gray-100"> <td class="px-4 w-1/4">{{ $directory->name1??$directory->name }} </td> <td class="px-4 w-1/4">{{ $directory->name2 }}</td> <td class="px-4 w-1/4">{{ $directory->user->department??'' }}</td> <td class="px-4 w-1/4">{{ $directory->directory }}</td> <td class="px-4 w-1/4">{{ $directory->user->mobile??'' }}</td> </tr> @endforeach </tbody> </table> </div> {{-- End Of Phonebook Table --}} {{-- Pagination and Records Count info --}} <div class="p-2 bg-gray-200"> {{ $phonebook->links('vendor.pagination.livewire-tailwind') }} </div> {{-- End Of Pagination and Records Count info --}} </div>
Code for the NON-working Livewire component and Blade view:
<?php namespace AppModels; use IlluminateSupportFacadesAuth; use IlluminateDatabaseEloquentModel; use IlluminateDatabaseEloquentFactoriesHasFactory; use LivewireWithPagination; class Phonecall extends Model { use HasFactory; use WithPagination; protected $table = "md_phonecall"; // custom method for "live" searching from livewire component public static function search($query) { return empty($query) ? static::query() : static::where('dialednumber', 'like', '%' . $query . '%') ->orWhere('startdate', 'like', $query) ->orWhere('starttime', 'like', $query) ->orWhere('chargednumber', 'like', '%' . $query . '%') ->orWhere('conditioncode', 'ilike', '%' . $query . '%'); } // 'conditioncode' accessor for casting integer value from DB to String shown in table View public function getConditioncodeAttribute($value) { switch ($value) { case 7; return 'Incoming call (' . $value . ')'; case 8; return 'Internal call (' . $value . ')'; case 10; return 'Outgoing call (' . $value . ')'; case 15; return 'Transfer call (' . $value . ')'; case 19; return 'Outgoing call (' . $value . ')'; case 23; return 'Missed call (' . $value . ')'; case 25; return 'Internal call (' . $value . ')'; case 28; return 'Outgoing call (' . $value . ')'; case 29; return 'Outgoing call (' . $value . ')'; default; return 'Unknown type (' . $value . ')'; } } // Local Scope for filtering only calls belonging to logged User public function scopeMycalls($query) { return $query->where('chargednumber', '=', Auth::user()->ext)->orWhere('dialednumber', '=', Auth::user()->ext); } /** * Scope a query to only include phone calls of a given type. * * @param IlluminateDatabaseEloquentBuilder $query * @param mixed $type * @return IlluminateDatabaseEloquentBuilder */ public function scopeOfType($query, $type) { if ($type === 'all') return $query->where('conditioncode', 'like', '%'); if ($type === 'incoming') return $query->where('conditioncode', '=', 7); if ($type === 'outgoing') return $query->where('conditioncode', '=', 10)->orWhere('conditioncode', '=', 19)->orWhere('conditioncode', '=', 28); if ($type === 'missed') return $query->where('conditioncode', '=', 23); if ($type === 'internal') return $query->where('conditioncode', '=', 8)->orWhere('conditioncode', '=', 15)->orWhere('conditioncode', '=', 25); } public function scopePeriod($query, $from, $to) { return $query->whereBetween('startdate', [$from, $to]); } public function scopeOfExtension($query, $extension) { return $query->where('dialednumber', 'like', '%' . $extension . '%') ->orWhere('chargednumber', 'like', '%' . $extension . "%"); } }
<?php namespace AppHttpLivewire; use LivewireComponent; use LivewireWithPagination; class MyPhonecallsTable extends Component { use WithPagination; public $perPage = 10; public $search = ''; public $calltype = 'all'; public function render() { return view('mycalls.my-phonecalls-table', [ 'mycalls' => AppModelsPhonecall::search( $this->search )->mycalls() ->ofType($this->calltype) ->orderBy('startdate', 'desc')->orderBy('starttime', 'desc')->paginate($this->perPage), ]); } /** * Livewire Lifecycle Hook */ public function updatingSearchInput(): void { $this->resetPage(); } }
<div> <div class="flex items-center justify-between"> {{-- Search Input --}} <div class="relative text-gray-700 focus-within:text-gray-600 m-2"> <span class="absolute inset-y-0 left-0 flex items-center pl-2"> <button type="submit" class="p-1 focus:outline-none focus:shadow-outline"> <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="w-6 h-6"> <path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path> </svg> </button> </span> <input wire:model="search" type="search" name="q" class="py-2 text-sm text-gray bg-gray-100 rounded-md pl-10 focus:outline-none focus:bg-gray-200 focus:text-gray-700" placeholder="{{ __('Search...') }}" autocomplete="off"> </div> {{-- End Of Search Input --}} {{-- Call type selector --}} @include('partials.calltypes') {{-- End of Call type selector --}} {{-- Paginator Select Dropdown --}} <div> <span>{{ __('Per page:') }}</span> <div class="inline-block relative mr-2"> <select wire:model="perPage" class="block appearance-none w-full bg-white border border-gray-400 hover:border-gray-500 px-4 py-2 pr-8 rounded shadow leading-tight focus:outline-none focus:shadow-outline"> <option>10</option> <option>25</option> <option>50</option> </select> <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700"> <svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"> <path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z" /></svg> </div> </div> </div> {{-- End Of Paginator Select Dropdown --}} </div> {{-- Phonecalls Table --}} <div class="container p-2" id="my-calls"> <table class="text-center w-full" id="my-calls-table"> <thead class="bg-gray-400 flex text-white w-full item-center rounded-md"> <tr class="flex w-full py-2"> <th class="px-4 w-1/5">{{ __('Date') }}</th> <th class="px-4 w-1/5">{{ __('Time') }}</th> <th class="px-4 w-1/5">{{ __('Duration') }}</th> <th class="px-4 w-1/5">{{ __('Calling No') }}</th> <th class="px-4 w-1/5">{{ __('Called No') }}</th> {{-- <th class="px-4 w-1/6">{{ __('Call Type') }}</th> --}} </tr> </thead> <!-- Remove the nasty inline CSS fixed height on production and replace it with a CSS class — this is just for demonstration purposes! --> <tbody class=""> @foreach ($mycalls as $mycall) <tr class="flex w-full py-2 hover:bg-gray-100"> <td class="px-4 w-1/5"> {{ CarbonCarbon::createFromFormat('Y-m-d',$mycall->startdate)->format('d.m.Y.')}} </td> <td class="px-4 w-1/5"> {{ CarbonCarbon::createFromFormat('H:i:s', $mycall->starttime, 'UTC')->tz('Europe/Belgrade')->toTImeString() }} </td> <td class="px-4 w-1/5"> {{ gmdate('H:i:s', $mycall->duration) }}</td> <td class="px-4 w-1/5">{{ $mycall->chargednumber}}</td> <td class="px-4 w-1/5">{{ $mycall->dialednumber}}</td> {{-- <td class="px-4 w-1/6">{{ $phonecall->conditioncode}}</td> --}} </tr> @endforeach </tbody> </table> </div> {{-- End Of Phonecalls Table --}} {{-- Pagination and Records Count info --}} <div class="p-2 bg-gray-200"> {{ $mycalls->links('vendor.pagination.livewire-tailwind') }} </div> {{-- End Of Pagination and Records Count info --}} </div>
While I’m waiting for any suggestion, I’ll try one more clean Laravel/Jetstream/Livewire install. Thanks in advance, any help is much appreciated!
Advertisement
Answer
By looking at my code one more time, I’ve noticed some “leftovers” in model classes. In model class there shouldn’t be “use WithPagination;” Livewire trait! When I removed “use” statemnets and cleared view cache, everything is working OK!