Belajar Livewire 4 #9 – Index Page Component

Tutorial lengkap Livewire 4 dan Laravel 12 yang membahas konsep, fitur terbaru, dan praktik terbaik membangun aplikasi web modern tanpa ribet JavaScript. Materi disusun bertahap dari dasar hingga studi kasus nyata dengan penjelasan mengalir dan mudah dipahami.

✅ Telah dilihat 163 kali

Rating: 5.00 ⭐

... 04 February 2026, 06:27

Index Page Component

Pada materi kali ini, kita akan membuat page component yang berfungsi untuk menampilkan data post yang sudah kita simpan di database.

Di Livewire versi 4, proses ini menjadi jauh lebih sederhana karena adanya fitur single file component. Artinya, class component dan tampilan Blade digabung dalam satu file, sehingga struktur kode menjadi lebih ringkas dan mudah dipahami.


Membuat Page Component

Untuk membuat halaman index post, silakan jalankan perintah berikut di terminal (pastikan masih berada di direktori project Laravel):

php artisan make:livewire pages::post.index

Penjelasan Perintah

Mari kita pahami perintah ini secara perlahan.

  • php artisan make:livewire Digunakan untuk membuat komponen Livewire baru
  • pages::post.index Menunjukkan bahwa komponen ini adalah page component, bukan sekadar komponen biasa

Dengan penamaan ini, Livewire akan:

  • Membuat komponen dalam struktur folder pages/post
  • Menjadikannya sebagai halaman penuh (full page component)

Hasil dari Perintah Ini

Setelah perintah dijalankan, Livewire akan membuat sebuah file di lokasi:

 INFO  Livewire component [C:\Users\LagiKoding\Desktop\simple-livewire4\resources\views/pages/post/⚡index.blade.php] created successfully.

Di dalam file ini:

  • Terdapat kode PHP (class component)
  • Sekaligus kode Blade (tampilan)

Inilah yang disebut single page component di Livewire 4.

Konfigurasi File index.blade.php

Silakan buka file yang baru saja dibuat kemudian ubah menjadi seperti berikut ini:

<?php

use App\Models\Post;
use Livewire\Component;
use Livewire\WithPagination;

new class extends Component
{
    use WithPagination;

    protected $paginationTheme = 'tailwind';

    public ?int $deleteId = null;

    public function confirmDelete($id)
    {
        $this->deleteId = $id;
    }

    public function delete()
    {
        Post::findOrFail($this->deleteId)->delete();

        $this->reset('deleteId');

        session()->flash('message', 'Post berhasil dihapus.');
    }

    public function render()
    {
        return $this->view([
            'posts' => Post::latest()->paginate(2),
        ])
            ->layout('layouts::app')
            ->title('Posts List');
    }
};
?>
<div class="space-y-6">

    {{-- Flash Message --}}
    @if (session()->has('message'))
    <div class="bg-green-500/10 border border-green-500/20
                    text-green-700 px-4 py-3 rounded-xl backdrop-blur">
        {{ session('message') }}
    </div>
    @endif

    {{-- Header --}}
    <div class="flex items-center justify-between">
        <h1 class="text-2xl font-semibold tracking-tight text-gray-900">
            Posts
        </h1>

        <a href="/posts/create" wire:navigate
            class="inline-flex items-center gap-2 px-4 py-2 rounded-xl
                  bg-indigo-600 text-white text-sm font-medium
                  hover:bg-indigo-700 transition shadow">
            + Add Post
        </a>
    </div>

    {{-- Table Card --}}
    <div class="rounded-2xl bg-white/70 backdrop-blur-xl
                border border-white/30 shadow-xl overflow-hidden">

        <div class="overflow-x-auto">
            <table class="min-w-full text-sm">
                <thead class="bg-white/60 border-b border-white/30">
                    <tr class="text-gray-700">
                        <th class="px-6 py-4 text-left">Image</th>
                        <th class="px-6 py-4 text-left">Title</th>
                        <th class="px-6 py-4 text-left">Content</th>
                        <th class="px-6 py-4 text-center w-40">Actions</th>
                    </tr>
                </thead>

                <tbody class="divide-y divide-white/30">
                    @forelse ($posts as $post)
                    <tr class="hover:bg-white/50 transition">
                        <td class="px-6 py-4">
                            <img
                                src="{{ asset('storage/posts/' . $post->image) }}"
                                class="w-28 h-20 object-cover rounded-lg shadow">
                        </td>

                        <td class="px-6 py-4 font-medium text-gray-900">
                            {{ $post->title }}
                        </td>

                        <td class="px-6 py-4 text-gray-600 max-w-md">
                            <div class="line-clamp-3">
                                {!! $post->content !!}
                            </div>
                        </td>

                        <td class="px-6 py-4 text-center space-x-3">
                            <a href="/posts/{{ $post->id }}/edit" wire:navigate
                                class="text-indigo-600 font-medium hover:underline">
                                Edit
                            </a>

                            <button
                                wire:click="confirmDelete({{ $post->id }})"
                                class="text-red-600 font-medium hover:underline">
                                Delete
                            </button>

                        </td>
                    </tr>
                    @empty
                    <tr>
                        <td colspan="4" class="py-10 text-center text-gray-500">
                            Data Post belum tersedia.
                        </td>
                    </tr>
                    @endforelse
                </tbody>
            </table>
            {{-- Delete Confirmation Modal --}}
            @if ($deleteId)
            <div
                x-data
                x-cloak
                class="fixed inset-0 z-50 flex items-center justify-center">

                <!-- Backdrop -->
                <div
                    class="absolute inset-0 bg-black/40 backdrop-blur-sm"
                    wire:click="$set('deleteId', null)">
                </div>

                <!-- Modal -->
                <div
                    class="relative w-full max-w-md rounded-2xl bg-white shadow-2xl p-6
                   animate-in fade-in zoom-in duration-200">

                    <div class="flex items-center gap-3">
                        <div class="flex h-10 w-10 items-center justify-center
                            rounded-full bg-red-100 text-red-600">
                            🗑️
                        </div>

                        <h2 class="text-lg font-semibold text-gray-900">
                            Hapus Post?
                        </h2>
                    </div>

                    <p class="mt-3 text-sm text-gray-600">
                        Post yang dihapus tidak dapat dikembalikan.
                        Apakah kamu yakin ingin melanjutkan?
                    </p>

                    <div class="mt-6 flex justify-end gap-3">
                        <button
                            wire:click="$set('deleteId', null)"
                            class="px-4 py-2 rounded-xl text-sm font-medium
                           bg-gray-100 hover:bg-gray-200 transition">
                            Batal
                        </button>

                        <button
                            wire:click="delete"
                            class="px-4 py-2 rounded-xl text-sm font-medium
                           bg-red-600 text-white hover:bg-red-700 transition shadow">
                            Ya, Hapus
                        </button>
                    </div>
                </div>
            </div>
            @endif

        </div>
    </div>

    {{-- Pagination --}}
    <div class="pt-2">
        {{ $posts->links() }}
    </div>
</div>

Import dan Deklarasi Component

use App\Models\Post;
use Livewire\Component;
use Livewire\WithPagination;
  • Post → model untuk berinteraksi dengan tabel posts
  • Component → base class Livewire
  • WithPagination → trait untuk pagination bawaan Livewire

Mengaktifkan Pagination

use WithPagination;

protected $paginationTheme = 'tailwind';

Artinya:

  • Pagination Livewire diaktifkan
  • Tampilan pagination menggunakan Tailwind CSS, bukan Bootstrap

Properti $deleteId

public ?int $deleteId = null;

Properti ini berfungsi untuk:

  • Menyimpan ID post yang ingin dihapus
  • Menjadi penanda apakah modal konfirmasi hapus ditampilkan atau tidak

Jika nilainya null → modal tidak muncul Jika berisi ID → modal muncul


Konfirmasi Hapus

public function confirmDelete($id)
{
    $this->deleteId = $id;
}

Method ini dipanggil saat tombol Delete diklik. Fungsinya hanya satu: menyimpan ID post ke $deleteId


Proses Hapus Data

public function delete()
{
    Post::findOrFail($this->deleteId)->delete();

    $this->reset('deleteId');

    session()->flash('message', 'Post berhasil dihapus.');
}

Alurnya:

  1. Cari data berdasarkan ID
  2. Hapus dari database
  3. Reset $deleteId agar modal tertutup
  4. Tampilkan flash message

Method render()

return $this->view([
    'posts' => Post::latest()->paginate(2),
])
    ->layout('layouts::app')
    ->title('Posts List');

Di sinilah halaman dirender:

  • Post::latest() → data terbaru di atas
  • paginate(2) → 2 data per halaman
  • layout('layouts::app') → menggunakan layout utama
  • title('Posts List') → title halaman

Bagian View (Tampilan)

Sekarang kita masuk ke HTML + Blade.


Flash Message

@if (session()->has('message'))

Digunakan untuk menampilkan pesan sukses setelah:

  • data berhasil dihapus

Pesan ini muncul satu kali lalu hilang otomatis.


Header Halaman

<h1>Posts</h1>
<a href="/posts/create" wire:navigate>+ Add Post</a>
  • Judul halaman
  • Tombol tambah data
  • wire:navigate → navigasi tanpa reload halaman (Livewire 4)

Tabel Data Post

Di dalam tabel ini:

  • Image → thumbnail gambar
  • Title → judul post
  • Content → isi post (dipotong 3 baris)
  • Actions → Edit & Delete
@forelse ($posts as $post)
  • Jika ada data → tampilkan baris
  • Jika kosong → tampilkan pesan Data Post belum tersedia

Menampilkan Gambar

<img src="{{ asset('storage/posts/' . $post->image) }}">

Ini berarti:

  • Gambar diambil dari storage/posts
  • Bergantung pada storage link yang sudah kita aktifkan sebelumnya

Tombol Edit & Delete

<a href="/posts/{{ $post->id }}/edit" wire:navigate>Edit</a>
<button wire:click="confirmDelete({{ $post->id }})">Delete</button>
  • Edit → pindah halaman
  • Delete → memicu modal konfirmasi

Modal Konfirmasi Hapus

@if ($deleteId)

Modal hanya muncul jika $deleteId tidak null.

Isi modal:

  • Judul konfirmasi
  • Penjelasan risiko
  • Tombol Batal
  • Tombol Ya, Hapus

Tombol hapus akan memanggil:

wire:click="delete"

Yang berarti memanggil method delete() di class component.


Pagination

{{ $posts->links() }}

Menampilkan pagination otomatis:

  • Sudah terhubung dengan Livewire

  • Tidak reload halaman

  • Styling Tailwind


Konfigurasi Rute

Langkah berikutnya adalah menghubungkan page component Livewire yang sudah kita buat ke dalam sistem routing Laravel.

Silakan buka file routes/web.php, lalu sesuaikan isinya menjadi seperti berikut:

<?php

use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    return view('welcome');
});

Route::livewire('/posts', 'pages::post.index');

Penjelasan

Mari kita pahami pelan-pelan apa yang terjadi di sini.

1. Route Halaman Utama (/)

Route::get('/', function () {
    return view('welcome');
});

Baris ini adalah route default Laravel. Saat pengguna mengakses URL utama aplikasi (/), Laravel akan menampilkan file:

resources/views/welcome.blade.php

2. Route Livewire Page Component

Route::livewire('/posts', 'pages::post.index');

Inilah bagian paling penting. Pada Livewire 4, kita bisa langsung menghubungkan URL ke single file component tanpa controller tambahan.

Artinya:

  • URL: /posts

  • Akan memanggil file:

    resources/views/livewire/pages/post/index.blade.php
    
  • File tersebut berisi class component + blade sekaligus

Livewire otomatis:

  • Menjalankan class component

  • Me-render view

  • Menggunakan layout yang sudah kita set:

    ->layout('layouts::app')
    

Tanpa controller, tanpa return view manual. Lebih ringkas dan modern.


Jalankan Project

Setelah semua konfigurasi selesai, sekarang saatnya menjalankan aplikasi Laravel yang sudah kita buat.

Silakan buka terminal di VS Code, lalu jalankan perintah berikut:

composer run dev

Penjelasan

Perintah ini adalah shortcut resmi Laravel yang sangat membantu saat proses development.

Ketika teman-teman menjalankan:

composer run dev

Laravel akan secara otomatis:

  1. Menjalankan php artisan serve → untuk menyalakan server Laravel
  2. Menjalankan npm run dev → untuk meng-compile Tailwind CSS, Vite, dan asset frontend lainnya

Semua proses tersebut berjalan bersamaan dalam satu perintah. Maka jika kita buka url http://127.0.0.1:8000/posts akan terlihat seperti pada gambar berikut ini:

Pada materi berikutnya, kita akan lanjutkan dengan membuat page component untuk create data.

Daftar eBook