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.
Creating Livewire Component
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'); }}
Form Validation
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.
ProductsCreate.php should inherit:
use Livewire\Attributes\Rule;
Thanks
I guess there might be a mistake. The file name below routes/web.php showed: resources/livewire/products-create.blade.php, which cantains 'Add new product' button. I guess this file should be: resources/livewire/products.blade.php
There are two code blocks with the heading
resources/livewire/products-create.blade.php:
but the first one should actually beresources/livewire/products.blade.php:
Thanks.
If someone got this error "SQLSTATE[HY000]: General error: 1364 Field 'category_id' doesn't have a default value".
you just need to add the "category_id" to your Product Model filable array;
protected $fillable = [ 'name', 'description', 'category_id', ];
For those that have the exception below:
Livewire page component layout view not found: [components.layouts.app]
You need to create the the directory config/livewire.php with this code below:
return [ // 'layout' => 'layouts.app' ];
Config files should be published not created manually
Also, you should first check the 6th lesson //lesson/livewire-3/full-page-components-replace-laravel-controllers
You're right, thanks, I ran the command to publish
php artisan livewire:publish --config
.