Courses

Livewire 3 From Scratch: Practical Course

New Product Form: Full-Page Component

Summary of this lesson:
- Setting up full-page Livewire component
- Implementing form validation
- Using validation attributes
- Managing form submissions

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>

add new product button

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');
}
}

products create form

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>

validation messages

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?

category validation message

For more on how you can customize error messages, you can check at the official documentation.

Previous: Delete Table Data with Confirmation Prompt
avatar

ProductsCreate.php should inherit: use Livewire\Attributes\Rule;

avatar
You can use Markdown
avatar

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

avatar
You can use Markdown
avatar

There are two code blocks with the heading resources/livewire/products-create.blade.php: but the first one should actually be resources/livewire/products.blade.php:

avatar
You can use Markdown
avatar
Francisco Ferreira Roque Júnior

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', ];

avatar
You can use Markdown
avatar

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' ];
avatar

Config files should be published not created manually

avatar

You're right, thanks, I ran the command to publish php artisan livewire:publish --config.

avatar
You can use Markdown
avatar
You can use Markdown