Livewire Parent-Child Dropdowns: 2-Level, 3-Level, and Select2 Alternatives

When creating forms it is pretty common to use two <select> dropdown fields depending on each other, with a parent-child relationship. In this tutorial, we will show to use Livewire Lifecycle Hooks to implement exactly that.

Also, we will make those <select> inputs even better by using Virtual Select and Tom Select JavaScript libraries.

The final result we will achieve using Tom Select:

final result using tom select

Table of Contents

  • 2-level Dropdown
  • 3-level Dropdown
  • Edit Form with 3-level Dropdown
  • Virtual Select library
  • Tom Select library

And, of course, link to the repository will be posted at the end of this tutorial.

Let's go?


2-level Dropdown

First, we will take a look at two select inputs. For this we will have two Models:

  • Category (string: name).
  • Product (string: name, foreignId: category_id).

And a Livewire component called CategoryProduct.

After selecting Category we will show all Products that belong to that category.

First, we need to set two public properties $categories and $products where the list of both will be set. And when the component gets mounted we set them.

use Livewire\Component;
use App\Models\Category;
use Illuminate\Support\Collection;
use Illuminate\Contracts\View\View;
 
class CategoryProduct extends Component
{
public Collection $categories;
 
public Collection $products;
 
public function mount(): void
{
$this->categories = Category::all();
$this->products = collect();
}
 
public function render(): View
{
return view('livewire.category-product');
}
}

And simple form with two select inputs.

<div>
<form wire:submit.prevent="submit">
<div>
<label class="block text-sm font-medium text-gray-700" for="category"> Category* </label>
<select wire:model="category" name="category"
class="mt-2 w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500" required>
<option value="">-- choose category --</option>
@foreach ($categories as $category)
<option value="{{ $category->id }}">{{ $category->name }}</option>
@endforeach
</select>
</div>
 
<div class="mt-4">
<label class="block text-sm font-medium text-gray-700" for="product"> Product* </label>
<select wire:model="product" name="product"
class="mt-2 w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500" required>
@if($products->isEmpty())
<option value="">-- choose category first --</option>
@endif
@foreach ($products as $product)
<option value="{{ $product->id }}">{{ $product->name }}</option>
@endforeach
</select>
</div>
 
<x-primary-button class="mt-4">
Submit
</x-primary-button>
</form>
</div>

2-level dropdown

Now we bind these inputs to category and product.

class CategoryProduct extends Component
{
public ?int $category = null;
 
public ?int $product = null;
 
public Collection $categories;
 
public Collection $products;
 
public function mount(): void
{
$this->categories = Category::all();
$this->products = collect();
}
// ...
}

After selecting the category we get its ID by which we can search for the products. For this, we need to use Lifecycle Hooks.

class CategoryProduct extends Component
{
public ?int $category = null;
 
public ?int $product = null;
 
public Collection $categories;
 
public Collection $products;
 
public function mount(): void
{
$this->categories = Category::all();
$this->products = collect();
}
 
public function updatedCategory($value): void
{
$this->products = Product::where('category_id', $value)->get();
$this->product = $this->products->first()->id ?? null;
}
 
public function render(): View
{
return view('livewire.category-product');
}
}

Here we add a new method updatedCategory. This will run after the property called category is updated and sends its value. All that's left to do is get the products list and set the first product to be selected.

selected 2-level dropdown

All that is left is to implement the Submit button action with usual logic like validation, creating a record, redirecting, etc.

use App\Models\Order;
 
class CategoryProduct extends Component
{
// ...
public function submit()
{
// Do validation
Order::create(['product_id' => $this->product]);
// Other related things for the creation process and redirect
}
// ...
}

3-level Dropdown

For the third selection, we will add...

The full tutorial [17 mins, 3321 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