Livewire v3 introduced Form Objects to offload the field logic from the Component. In this tutorial, we'll build the create/edit modal forms powered by the Wire Elements package and reuse the same Livewire component and Form Object.
Preparation: Laravel Project
For the Laravel project visual design, we will use our own starter kit Laravel Breeze Pages Skeleton.
In the Model Product
we will have two DB fields: name
and description
.
database/migrations/xxx_create_products_table.php:
public function up(): void{ Schema::create('products', function (Blueprint $table) { $table->id(); $table->string('name'); $table->text('description'); $table->timestamps(); });}
app/Models/Product.php:
class Product extends Model{ protected $fillable = [ 'name', 'description', ];}
Install Livewire and Customize Breeze
First, of course, we need to install install Livewire.
composer require livewire/livewire
Next, because Alpine.js is baked into the core of Livewire, we need to remove it from our Breeze-powered skeleton. If you don't use Laravel Breeze, you can skip this step.
resources/js/app.js:
import './bootstrap'; import Alpine from 'alpinejs'; window.Alpine = Alpine; Alpine.start();
And recompile assets.
npm run prod
The last thing is changing the main layout path.
Livewire looks for a layout in resources/views/components/layouts/app.blade.php
, but our Breeze-based Starter kit has it in a different place, so we need to set it in the Livewire config.
php artisan livewire:publish --config
config/livewire.php:
return [ // ... 'layout' => 'components.layouts.app', 'layout' => 'layouts.app', // ...];
And that's it, we can use Livewire in our project. Now, let's create a Livewire component to show the products list.
Livewire Component: Products List
php artisan make:livewire ProductList
app/Livewire/ProductList.php:
use App\Models\Product;use Illuminate\Contracts\View\View; class ProductList extends Component{ public function render(): View { return view('livewire.product-list', [ 'products' => Product::all(), ]); }}
Let's add a link to the navigation and show the products table.
resources/views/layouts/navigation.blade.php:
// ... <div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex"> <x-nav-link :href="route('users.index')" :active="request()->routeIs('users.index')"> {{ __('Users') }} </x-nav-link> <x-nav-link :href="route('products')" :active="request()->routeIs('products')"> {{ __('Products') }} </x-nav-link> </div> // ...
resources/views/livewire/product-list.blade.php:
<div> <x-slot name="header"> <h2 class="text-xl font-semibold leading-tight text-gray-800"> {{ __('Products') }} </h2> </x-slot> <div class="py-12"> <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 overflow-hidden overflow-x-auto bg-white border-b border-gray-200"> <div class="min-w-full align-middle"> <table class="min-w-full border divide-y divide-gray-200"> <thead> <tr> <th class="px-6 py-3 text-left bg-gray-50"> <span class="text-xs font-medium leading-4 tracking-wider text-gray-500 uppercase">Name</span> </th> <th class="px-6 py-3 text-left bg-gray-50"> <span class="text-xs font-medium leading-4 tracking-wider text-gray-500 uppercase">Description</span> </th> <th class="px-6 py-3 text-left bg-gray-50"> </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 text-sm leading-5 text-gray-900 whitespace-no-wrap"> {{ $product->name }} </td> <td class="px-6 py-4 text-sm leading-5 text-gray-900 whitespace-no-wrap"> {{ $product->description }} </td> <td class="px-6 py-4 text-sm leading-5 text-gray-900 whitespace-no-wrap"> {{-- Edit Button --}} </td> </tr> @empty <tr class="bg-white"> <td colspan="3" class="px-6 py-4 text-sm leading-5 text-gray-900 whitespace-no-wrap"> No products found. </td> </tr> @endforelse </tbody> </table> </div> </div> </div> </div> </div></div>
Modal with Form Object
Now, let's create a component that will be a modal and...