Livewire 3 CRUD with Form Objects and Modal Wire Elements

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.

modal with unique rule


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>

products table


Modal with Form Object

Now, let's create a component that will be a modal and...

The full tutorial [10 mins, 1934 words] is only for Premium Members

Login Or Become a Premium Member for $129/year or $29/month
What else you will get:
  • 59 courses (1056 lessons, total 42 h 44 min)
  • 78 long-form tutorials (one new every week)
  • access to project repositories
  • access to private Discord

Recent Premium Tutorials