Blog FQSoft

Just Simple Code Documentation

Laravel

Membuat Komponen Table dengan Livewire 3

Faiq Himmah 11 January 2026

Kita akan membuat sebuah komponen tabel dengan livewire yag reuseable. dengan fitur yang ditawarkan hingga tulisan ini dibuat adalah :

  • Pencarian
  • Pagination

Komponen ini awalnya saya buat saat membuat project CMS dan hampir semua data yang ditampilkan berbentuk data tabular. Sehingga tidak efisien setiap ada modul baru selalu duplikasi code. bisa dibayangkan saat ada perubahan fitur table atau design table maka kita harus mengubah satu persatu. oleh karenanya komponen ini saya baut lebih modular agar bisa dipakai diberbagai komponen livewire yang membutuhkan tampilan data secara tabular.

Membuat class BaseTable

  • Semua komponen yang butuh tampilan tabular bisa extends dari class ini
  • Class ini saya letakkan di direktori app/Livewire/base sebagai pemisah pada class livewire lain
<?php

namespace App\Livewire\base;

use Livewire\Component;
use Livewire\WithPagination;

abstract class BaseTable extends Component{
    use WithPagination;
    public $search = '';
    public $perPage = 10;

    // Custom pagination variables
    public $currentPage = 1;
    public $totalPages = 1;
    public $totalRecords = 0;

    protected $queryString = [
        'search' => ['except' => ''],
        'currentPage' => ['except' => 1], 
    ];

    public function updatingSearch()
    {
        $this->currentPage = 1;
    }

    public function gotoPage($page)
    {
        $this->currentPage = max(1, min($page, $this->totalPages));
    }

    public function nextPage()
    {
        $this->gotoPage($this->currentPage + 1);
    }

    public function prevPage()
    {
        $this->gotoPage($this->currentPage - 1);
    }

    public function getPaginate($query){
        $total = $query->count();    
        $this->totalRecords = $total;
        $this->totalPages = max(1, ceil($total / $this->perPage));
        
        if ($this->currentPage > $this->totalPages) {
            $this->currentPage = 1;
        }
            
        return $query->skip(($this->currentPage - 1) * $this->perPage)
                ->take($this->perPage)
                ->get();
    }

    abstract public function show();
}

Penjelasan Kode :

  • Berupa abstract class, menunjukkan bahwa class ini murni blueprint atau tidak bisa dibuat object harus ada class yang extends
  • Extend ke Component livewire
  • Fungsi updatingSearch() adalah fungsi yang otomatis dijalankan saat variabel $search ada perubahan value. isi dari perintahnya adalah setiap ada aksi pencarian maka $currentPage kembali ke halaman 1. jadi untuk membuat fungsi seperti ini hanya di penamaan saja yaitu updatingNamaVariabel()
  • Fungsi getPaginate() untuk membuat pagination berdasarkan input dari variabel $query
  • Disini ada fungsi abstract yang menandakan suatu fungsi harus di-override oleh child class jika tidak ada maka akan terjadi error. fungsi itu bernama show(). untuk isinya bisa dilihat di langkah selanjutnya
  • Ingat dalam konsep OOP bahwa setiap variabel atau fungsi yang bersifat public di parent class akan menajadi milik child class 

Cara Menggunakan Komponen Table

untuk menggunaknnya kita harus buat komponen livewire yang extend pada class BaseTable diatas,

<?php

namespace App\Livewire\Admin;

use App\Livewire\base\BaseTable;
use App\Models\Post;

class PostTable extends BaseTable
{
    public Post $obj_post;

    public function mount(){
        
    }

    public function show(){
        $this->obj_post = new Post();
        $query = $this->obj_post->getAllPost($this->postType,null,null,$this->search,null,null);
        return parent::getPaginate($query);
    }

    public function delete($id){
        $this->obj_post->deletePost($id);
    }

    public function render()
    {   
        return view('livewire.admin.post-table',[
            'posts' => $this->show()
        ]);
    }
}

Penjelasan kode :

  • Extends class BaseTable
  • Override fungsi show()
  • fungsi show() harus return fungsi getPaginate() milik BaseClass untuk membuat custom pagination

Membuat View Komponen Table

View dari komponen table bersifat Component Anonymous dalam arti dia bukan komponen milik livewire. jadi dia beridir sendiri tetapi setiap variabel yang ada itu berasal dari class BaseTable. agar modular maka view table saya bagi dua file yaitu

  • data-table.blade.php
  • table-paginate.blade.php

Mengapa untuk paginate dipisahkan ? karena boleh jadi paginate ditampilkan di dua tempat yaitu umumnya dibagian atas dan bawah table. dua file ini baisanya saya letakkan di direktori resources/views/components/table. Berikut isi 2 file tersebut

data-table.blade.php

<div class="space-y-4">
    {{-- 🔍 Search Bar --}}
    @if ($searchable ?? false)
        <div class="flex justify-end">
            <div wire:loading class="text-center py-1 px-2">
                <div class="inline-block animate-spin rounded-full h-4 w-4 border-t-2 border-b-2 border-indigo-600"></div>
            </div>
            <div>
                <input wire:model="search"
                   wire:keydown.enter="show"
                   type="text"
                   placeholder="Search..."
                   class="w-full sm:w-64 rounded-md border border-gray-300 px-3 py-2 text-sm text-gray-700 focus:outline-none focus:ring-2 focus:ring-[#FCA311] focus:border-[#FCA311]" />
            </div>
        </div>
    @endif
    
    <x-table.table-paginate
        :totalRecords="$this->totalRecords" 
        :totalPages="$this->totalPages" 
        :currentPage="$this->currentPage" 
        :perPage="$this->perPage" 
    />
    {{-- 📋 Table --}}
    <div class="overflow-x-auto rounded-lg shadow-sm border border-gray-200">
        <table class="min-w-full divide-y divide-gray-200 text-sm">
            <thead class="bg-[#FCA311]/90 text-white">
                <tr>
                    @foreach ($headers as $header)
                        <th class="px-4 py-3 text-left font-semibold">{{ $header }}</th>
                    @endforeach
                </tr>
            </thead>
            <tbody class="divide-y divide-gray-100">
                {{ $slot }}
            </tbody>
        </table>

        {{-- 🕳️ Empty State --}}
        @if ($empty ?? false)
            <div class="text-center py-6 text-gray-500 text-sm">
                No data found.
            </div>
        @endif
    </div>

    {{-- 📄 Pagination Bottom --}}
    <x-table.table-paginate
        :totalRecords="$this->totalRecords" 
        :totalPages="$this->totalPages" 
        :currentPage="$this->currentPage" 
        :perPage="$this->perPage" 
    />
    
    <div wire:loading>
        <x-loading.loading-label label="Mohon tunggu, data sedang diproses..."/>
    </div>
</div>

table-paginate.blade.php

@if ($totalRecords > 1)
    <div class="flex flex-row-reverse items-center space-x-2 space-x-reverse">

        {{-- >> --}}
        <button wire:click="gotoPage({{ $totalPages }})"
            class="px-2 py-1 border rounded {{ $currentPage == $totalPages ? 'text-gray-400 border-gray-400' : '' }}"
            @if($currentPage == $totalPages) disabled @endif>
            &raquo;
        </button>

        {{-- > --}}
        <button wire:click="nextPage"
            class="px-2 py-1 border rounded {{ $currentPage == $totalPages ? 'text-gray-400 border-gray-400' : '' }}"
            @if($currentPage == $totalPages) disabled @endif>
            &rsaquo;
        </button>

        {{-- Input page --}}
        <div class="flex items-center space-x-1">
            <input type="number"
                   value="{{ $currentPage }}"
                   min="1"
                   max="{{ $totalPages }}"
                   wire:keydown.enter="gotoPage($event.target.value)"
                   class="w-12 py-1 text-center border rounded"
                   placeholder="Page">
            <span class="text-sm">of {{ $totalPages }}</span>
        </div>

        {{-- < --}}
        <button wire:click="prevPage"
            class="px-2 py-1 border rounded {{ $currentPage==1 ? 'text-gray-400 border-gray-400' : '' }}"
            @if($currentPage==1) disabled @endif>
            &lsaquo;
        </button>

        {{-- << --}}
        <button wire:click="gotoPage(1)"
            class="px-2 py-1 border rounded {{ $currentPage==1 ? 'text-gray-400 border-gray-400' : '' }}"
            @if($currentPage==1) disabled @endif>
            &laquo;
        </button>

        <div class="text-xs pt-1 ml-2">
            Menampilkan {{ ($currentPage - 1) * $perPage + 1 }} - 
            {{ min($currentPage * $perPage, $totalRecords) }} 
            dari {{ $totalRecords }} data
        </div>
    </div>
@endif

Penjelasan Kode :

Bisa kita lihat bahwa variabel PHP yang ada di file table-paginate.blade.php berasala dari class BaseClass

 

loading-label.blade.php

di file data-table.blade.php ada dua komponen yaitu komponen pagiantion yang sudah kita buat di atas. Sedangkan komponen kedua adalah komponen loading agar saat aksi table sperti searching dan berpindah page bisa muncul loading. untuk itu kita harus buat komponen loading. komponen ini sama-sama bersifat anonymous  kita letakkan di direktori resources/views/components/loading dengan nama loading-label.blade.php 

@props(['label' => 'label','posY'=>'bottom','posX'=>'right'])
<div class="fixed inset-0 bg-black bg-opacity-0 z-50">
    <div class="absolute {{$posY}}-4 {{$posX}}-4 bg-black px-4 py-3 rounded-md shadow-lg flex items-center space-x-3">
        <svg class="animate-spin h-5 w-5 text-blue-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
            <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
            <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z"></path>
        </svg>
        <span class="text-white font-medium">{{$label}}</span>
    </div>
</div>

Sekarang Saatnya Table Ditampilkan di View Komponen PostTable

<div wire:loading.remove>
    <x-table.data-table
    :headers="['Title', 'Author', 'Category', 'Tags', 'Status & Date']"
    :searchable="true"
    :empty="false"
    :search="$this->search"
>
    @foreach ($posts as $p)
        <tr class="hover:bg-[#FCA311]/10 transition duration-150">
            <td class="px-4 py-3">
                <div class="flex flex-col">
                    <span class="font-medium text-gray-900">{{ $p->post_title }}</span>
            
                    <div class="flex space-x-2 mt-1 text-sm text-blue-600">
                        <a href="{{url('admin/form-post?type='.$postType.'&id='.$p->ID)}}" target="_blank" class="hover:underline cursor-pointer"><i class="fa-solid fa-pencil"></i></a>
                        <a class="hover:underline text-red-600 cursor-pointer"
                           onclick="if(confirm('Yakin ingin menghapus kategori ini?')) { @this.delete({{$p->ID}}) }"><i class="fa-solid fa-trash"></i></a>
                    </div>
                </div>
            </td>
            <td class="px-4 py-3">{{ $p->name }}</td>
            <td class="px-4 py-3">{!! $obj_post->extractTerm($p->ID)['category'] !!}</td>
            <td class="px-4 py-3">{!! $obj_post->extractTerm($p->ID)['tags'] !!}</td>
            <td class="px-4 py-3">
                <span class="inline-block px-2 py-1 text-xs font-semibold rounded-full {{ $p->post_status == 'publish' ? 'bg-[#FCA311]/20 text-[#FCA311]' : 'bg-gray-200 text-gray-600' }}">
                    {{ $p->post_status == 'publish' ? 'Published' : 'Draft' }}
                </span>
                <div class="text-xs text-gray-500 mt-1">{{ date('Y/m/d H:i', strtotime($p->post_date)) }}</div>
            </td>
        </tr>
    @endforeach
    
</x-data-table>

</div>

Penjelasan Kode :

  • Untuk memanggil anonymous component adalah dengan <x-table.data-table></x-table.data-table>
  • Beberapa parameter yang di-passing  adalah :header, :serachable, :empty dan :search

 

Kesimpulan

Untuk menggunakan kembali cukup lakukan

  • Komponen Livewire extends BaseTable
  • Di view livewire cukup panggil komponen <x-table.data-table></x-table.data-table> contohnya di langkah "Sekarang Saatnya Table Ditampilkan di View Komponen PostTable"
  • Dan hasilnya it's magic
laravel livewire