In this lesson, let's create a second Livewire component for creating a product. We can call this component in two ways.
The first one is the same way as we did with the Products
Livewire component making a controller that returns a View and calls the Livewire component. The second one is making the Livewire component a full-page and mapping it directly in the routes file.
This lesson will use the second method, the Livewire component, as a full page.
So first, we need to create a component.
php artisan make:livewire ProductsCreate
Now we can create a route and map it to this component and add a link to this route.
routes/web.php:
Route::get('products/create', \App\Livewire\ProductsCreate::class)->name('products.create');
resources/livewire/products.blade.php:
<div class="space-y-6"> <div class="flex justify-between"> <div class="space-x-8"> <input wire:model.live="searchQuery" type="search" id="search" placeholder="Search..."> <select wire:model.live="searchCategory" name="category"> <option value="0">-- CHOOSE CATEGORY --</option> @foreach($categories as $id => $category) <option value="{{ $id }}">{{ $category }}</option> @endforeach </select> </div> <a wire:navigate href="{{ route('products.create') }}" class="inline-flex items-center px-4 py-2 bg-gray-800 rounded-md font-semibold text-xs text-white uppercase tracking-widest"> Add new product </a> </div> // ...</div>
Now let's add a form and make it work by binding properties and making a method for creating the record.
resources/views/livewire/products-create.blade.php:
<form method="POST" wire:submit="save"> <div> <label for="name" class="block font-medium text-sm text-gray-700">Name</label> <input id="name" class="block mt-1 w-full border-gray-300 rounded-md shadow-sm" type="text" wire:model="name" /> </div> <div class="mt-4"> <label for="description" class="block font-medium text-sm text-gray-700">Description</label> <textarea id="description" class="block mt-1 w-full border-gray-300 rounded-md shadow-sm" wire:model="description"></textarea> </div> <div class="mt-4"> <label for="category">Category</label> <select wire:model="category_id" name="category" id="category" class="block mt-1 w-full border-gray-300 rounded-md shadow-sm"> <option value="0">-- CHOOSE CATEGORY --</option> @foreach($categories as $id => $category) <option value="{{ $id }}">{{ $category }}</option> @endforeach </select> </div> <button class="mt-4 px-4 py-2 bg-gray-800 rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700"> Save Product </button></form>
app/Livewire/ProductsCreate.php:
use Livewire\Component;use App\Models\Product;use App\Models\Category;use Illuminate\Support\Collection;use Illuminate\Contracts\View\View; class ProductsCreate extends Component{ public string $name = ''; public string $description = ''; public int $category_id; public Collection $categories; public function mount(): void { $this->categories = Category::pluck('name', 'id'); } public function save(): void { Product::create($this->only(['name', 'description', 'category_id'])); $this->redirect('/products'); } public function render(): View { return view('livewire.products-create'); }}
For now, we haven't done anything new that we haven't talked about earlier. Saving the product currently works, but what if the user presses the button when inputs are empty? We need to add validation.
Validation rules are added using the #[Rule]
attribute, and then in the save
method, we need to call validate
.
app/Livewire/ProductsCreate.php:
use Livewire\Attributes\Validate; class ProductsCreate extends Component{ #[Validate('required|min:3')] public string $name = ''; #[Validate('required|min:3')] public string $description = ''; #[Validate('required|exists:categories,id')] public int $category_id; public Collection $categories; // ... public function save(): void { $this->validate(); Product::create($this->only(['name', 'description', 'category_id'])); $this->redirect('/products'); } // ...}
And, of course, we need to show validation messages in the Blade file.
resources/views/filament/products-create.blade.php:
<form method="POST" wire:submit="save"> <div> <label for="name" class="block font-medium text-sm text-gray-700">Name</label> <input id="name" class="block mt-1 w-full border-gray-300 rounded-md shadow-sm" type="text" wire:model="name" /> @error('name') <span class="mt-2 text-sm text-red-600">{{ $message }}</span> @enderror </div> <div class="mt-4"> <label for="description" class="block font-medium text-sm text-gray-700">Description</label> <textarea id="description" class="block mt-1 w-full border-gray-300 rounded-md shadow-sm" wire:model="description"></textarea> @error('description') <span class="mt-2 text-sm text-red-600">{{ $message }}</span> @enderror </div> <div class="mt-4"> <label for="category">Category</label> <select wire:model="category_id" name="category" id="category" class="block mt-1 w-full border-gray-300 rounded-md shadow-sm"> <option value="0">-- CHOOSE CATEGORY --</option> @foreach($categories as $id => $category) <option value="{{ $id }}">{{ $category }}</option> @endforeach </select> @error('category_id') <span class="mt-2 text-sm text-red-600">{{ $message }}</span> @enderror </div> <button class="mt-4 px-4 py-2 bg-gray-800 rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700"> Save Product </button></form>
But as you can see, the category in the message says category id
. It would be great to change it. We can add an as
parameter to change how the property will be named for validation.
app/Livewire/ProductsCreate.php:
class ProductsCreate extends Component{ #[Validate('required|min:3')] public string $name = ''; #[Validate('required|min:3')] public string $description = ''; #[Validate('required|exists:categories,id', as: 'category')] public int $category_id; public Collection $categories; // ...}
Now the message is much better, right?
For more on how you can customize error messages, you can check at the official documentation.