Back to Course |
Livewire 3 From Scratch: Practical Course

Multi-Select and belongsToMany Relationship

In this lesson, we will see how to create multiple select input. We will change a category select dropdown from a single select to a multiple one.

multiple select form


DB Changes

Before making changes to the Livewire component, we need to make changes to the DB. First, we need a BelongsToMany relationship table.

database/migrations/xxxx_create_category_product_table.php:

Schema::create('category_product', function (Blueprint $table) {
$table->foreignId('category_id')->constrained()->cascadeOnDelete(;
$table->foreignId('product_id')->constrained()->cascadeOnDelete();
});

app/Models/Product.php:

use Illuminate\Database\Eloquent\Relations\BelongsToMany;
 
class Product extends Model
{
protected $fillable = [
'name',
'description',
'category_id',
'color',
'in_stock',
];
 
const COLOR_LIST = [
'red' => 'Red',
'green' => 'Green',
'blue' => 'Blue',
];
 
public function category(): BelongsTo
{
return $this->belongsTo(Category::class);
}
 
public function categories(): BelongsToMany
{
return $this->belongsToMany(Category::class);
}
}

Earlier, we added category_id to the Products table. You need to delete that migration. Otherwise, when creating a product, you will get an error. Don't forget to change the ProductFactory accordingly.

Also, I have changed the seeder to add two categories for each product.

$categories = collect(Category::pluck('id'));
Product::factory(50)
->create()
->each(function (Product $product) use ($categories) {
$product->categories()->sync($categories->random(2));
});

Livewire Component Changes

Now let's change the Products Livewire component to show multiple companies and fix the search.

app/Livewire/Products.php:

use Illuminate\Database\Eloquent\Builder;
 
class Products extends Component
{
// ...
 
public function render(): View
{
$products = Product::with('category')
$products = Product::with('categories')
->when($this->searchQuery !== '', fn(Builder $query) => $query->where('name', 'like', '%'. $this->searchQuery .'%'))
->when($this->searchCategory > 0, fn(Builder $query) => $query->where('category_id', $this->searchCategory))
->when($this->searchCategory > 0, function (Builder $query) {
$query->whereHas('categories', function (Builder $query2) {
$query2->where('id', $this->searchCategory);
});
})
->paginate(10);
 
return view('livewire.products', [
'products' => $products,
]);
}
}

resources/views/livewire/products.blade.php:

<div class="space-y-6">
// ...
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
@foreach($product->categories as $category)
<div>{{ $category->name }}</div>
@endforeach
{{ $product->category->name }}
</td>
// ...
</div>

showing multiple categories in table


Add Multiselect to the Form

First, add multiselect to the select input and change wire:model bind from form.category_id to form.productCategories. This new productCategories public property has to be an array that will be passed into a sync method.

resources/views/livewire/products-create.blade.php:

<form method="POST" wire:submit="save">
// ...
 
<div class="mt-4">
<label for="category">Category</label>
<select wire:model="form.category_id" name="category" id="category" class="block mt-1 w-full border-gray-300 rounded-md shadow-sm">
<select multiple wire:model="form.productCategories" 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('form.category_id')
@error('form.productCategories')
<span class="mt-2 text-sm text-red-600">{{ $message }}</span>
@enderror
</div>
 
// ...
</form>

app/Livewire/Forms/ProductsForm.php:

class ProductsForm extends Form
{
public ?Product $product;
#[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;
#[Validate('required|string')]
public string $color = '';
#[Validate('boolean')]
public bool $in_stock = true;
#[Validate('required|array', as: 'category')]
public array $productCategories = [];
 
// ...
}

After selecting categories, they will be put into the productCategories array.

multiple select form

All that is left to do is sync categories with the product when it is created or updated, and when setting the product in the setProduct method, add all product categories to the productCategories property.

app/Livewire/Forms/ProductsForm.php:

class ProductsForm extends Form
{
// ...
 
public function setProduct(Product $product): void
{
$this->product = $product;
$this->name = $product->name;
$this->description = $product->description;
$this->category_id = $product->category_id;
$this->color = $product->color;
$this->in_stock = $product->in_stock;
$this->productCategories = $product->categories()->pluck('id')->toArray();
}
 
public function save(): void
{
$this->validate();
 
$product = Product::create($this->all());
$product->categories()->sync($this->productCategories);
}
 
public function update(): void
{
$this->validate();
 
$this->product->update($this->all());
$this->product->categories()->sync($this->productCategories);
}
}

And that's it. Now when you select multiple categories, they will be attached to the product.