In this section, let's dive into more complex and practical stuff: we will create a CRUD with Livewire. Let's start by creating a table in Laravel, moving it to Livewire, and adding pagination to the table.
For the initial Laravel project, we will have a Product
Model and a page to list all the products.
database/migrations/xxxx_create_products_table.php:
Schema::create('products', function (Blueprint $table) { $table->id(); $table->string('name'); $table->text('description'); $table->timestamps();});
app/Models/Product.php:
use Illuminate\Database\Eloquent\Factories\HasFactory; class Product extends Model{ use HasFactory; protected $fillable = [ 'name', 'description', ];}
database/factories/ProductFactory.php:
class ProductFactory extends Factory{ public function definition(): array { return [ 'name' => $this->faker->name(), 'description' => $this->faker->text(50), ]; }}
database/seeders/DatabaseSeeder.php:
class DatabaseSeeder extends Seeder{ public function run(): void { Product::factory(50)->create(); }}
Don't forget to run Seeder when migrating database
php artisan migrate --seed
.
app/Http/Controllers/ProductController.php:
use App\Models\Product;use Illuminate\Contracts\View\View; class ProductController extends Controller{ public function index(): View { $products = Product::all(); return view('products.index', compact('products')); }}
routes/web.php:
Route::get('products', [\App\Http\Controllers\ProductController::class, 'index']);
resources/views/products/index.blade.php:
<x-app-layout> // ... layout header code <div class="min-w-full align-middle"> <table class="min-w-full divide-y divide-gray-200 border"> <thead> <tr> <th class="px-6 py-3 bg-gray-50 text-left"> <span class="text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">Name</span> </th> <th class="px-6 py-3 bg-gray-50 text-left"> <span class="text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">Description</span> </th> <th class="px-6 py-3 bg-gray-50 text-left"> </th> </tr> </thead> <tbody class="bg-white divide-y divide-gray-200 divide-solid"> @forelse($products as $product) <tr class="bg-white"> <td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900"> {{ $product->name }} </td> <td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900"> {{ $product->description }} </td> <td> <a href="#" class="inline-flex items-center px-4 py-2 bg-gray-800 rounded-md font-semibold text-xs text-white uppercase tracking-widest"> Edit </a> <a href="#" class="inline-flex items-center px-4 py-2 bg-red-600 rounded-md font-semibold text-xs text-white uppercase tracking-widest"> Delete </a> </td> </tr> @empty <tr> <td class="px-6 py-4 text-sm" colspan="3"> No products were found. </td> </tr> @endforelse </tbody> </table></div> // ... layout footer code </x-app-layout>
Notice: In this tutorial, we use the Laravel Breeze layout with x-app-layout
Blade component, but feel free to use any other layout. Read more in the docs.
After visiting the /products
page, you should see a table with the list of products.
Now let's move the table to the Livewire component. First, we need a component.
php artisan make:livewire Products
Next, the whole table code move into the Livewire components Blade file and instead call the Products
Livewire component.
resources/views/products/index.blade.php:
<x-app-layout> // ... layout header code <div class="min-w-full align-middle"> <table class="min-w-full divide-y divide-gray-200 border"> // ... </table></div> <livewire:products /> // ... layout footer code </x-app-layout>
resources/views/livewire/products.blade.php:
<div class="min-w-full align-middle"> <table class="min-w-full divide-y divide-gray-200 border"> // ... </table></div>
We must pass the products list to the view in the Livewire components class.
app/Livewire/Products.php:
use App\Models\Product;use Illuminate\Contracts\View\View; class Products extends Component{ public function render(): View { return view('livewire.products', [ 'products' => Product::all(), ]); }}
After refreshing the page visually, there shouldn't be any changes, but now the table is in the Livewire component.
To paginate Products
, there is no difference from how you would do it in the Controller. We use the paginate
method and call the links
method in the Blade to show pagination links.
app/Livewire/Products.php:
class Products extends Component{ public function render(): View { return view('livewire.products', [ 'products' => Product::all(), 'products' => Product::paginate(10), ]); }}
resources/views/livewire/products.blade.php:
<div class="space-y-6"> <div class="min-w-full align-middle"> <table class="min-w-full divide-y divide-gray-200 border"> // ... </table> </div> {{ $products->links() }} </div>
Remember, Livewire needs to have one root HTML element. That's why here we wrapped everything with a
<div>
element.
If you visit the page now, you will see an error.
We need to add a WithPagination
trait to use pagination with Livewire.
app/Livewire/Products.php:
use Livewire\WithPagination; class Products extends Component{ use WithPagination; public function render(): View { return view('livewire.products', [ 'products' => Product::paginate(10), ]); }}
And now we have a working pagination.
If you want to remove the ?page
from the URL, this can be done by adding an additional Livewire\WithoutUrlPagination
trait.
app/Livewire/Products.php:
use Livewire\WithoutUrlPagination; class Products extends Component{ use WithPagination; use WithoutUrlPagination; public function render(): View { return view('livewire.products', [ 'products' => Product::paginate(10), ]); }}