Back to Course |
Livewire 3 From Scratch: Practical Course

Dependent Dropdowns: Country and Cities

In this course section, we will build four small demo projects to practice Livewire more. The first project is a dependent dropdown, like a parent and a child.

When we will choose a country, the cities list will be refreshed.

dependent dropdowns


DB Structure

First, let's quickly see what the Migrations and Models look like.

database/migrations/xxx_create_countries_table.php:

Schema::create('countries', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});

app/Models/Country.php:

use Illuminate\Database\Eloquent\Relations\HasMany;
 
class Country extends Model
{
protected $fillable = [
'name',
];
 
public function cities(): HasMany
{
return $this->hasMany(City::class);
}
}

database/migrations/xxx_create_cities_table.php:

Schema::create('cities', function (Blueprint $table) {
$table->id();
$table->foreignId('country_id');
$table->string('name');
$table->timestamps();
});

app/Models/City.php:

class City extends Model
{
protected $fillable = [
'country_id',
'name',
];
}

And a simple seeder to have some data in the DB:

$country = Country::create(['name' => 'United Kingdom']);
$country->cities()->create(['name' => 'London']);
$country->cities()->create(['name' => 'Manchester']);
$country->cities()->create(['name' => 'Liverpool']);
 
$country = Country::create(['name' => 'United States']);
$country->cities()->create(['name' => 'Washington']);
$country->cities()->create(['name' => 'New York City']);
$country->cities()->create(['name' => 'Denver']);

Livewire Component

For the Livewire component, we will name it just Dropdowns.

php artisan make:livewire Dropdowns

In the component, we first must get the list of countries in the mount method and set the cities list to an empty collection.

app/Livewire/Dropdowns.php:

use App\Models\Country;
use Illuminate\Support\Collection;
 
class Dropdowns extends Component
{
public Collection $countries;
public Collection $cities;
 
public function mount(): void
{
$this->countries = Country::pluck('name', 'id');
$this->cities = collect();
}
 
// ...
}

Now let's add inputs in the Blade file.

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

<form method="POST" wire:submit="save">
<div>
<label for="country">Country</label>
<select wire:model.live="country" id="country" class="block mt-1 w-full border-gray-300 rounded-md shadow-sm">
<option value="0">-- choose country --</option>
@foreach($countries as $id => $country)
<option value="{{ $id }}">{{ $country }}</option>
@endforeach
</select>
</div>
 
<div class="mt-4">
<label for="city">City</label>
<select wire:model="city" id="city" class="block mt-1 w-full border-gray-300 rounded-md shadow-sm">
@if ($cities->count() == 0)
<option value="">-- choose country first --</option>
@endif
@foreach($cities as $city)
<option value="{{ $city->id }}">{{ $city->name }}</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
</button>
</form>

The important part here is to use live for the country input. Otherwise, it won't update the cities list after selecting a country.

empty dropdowns form

Next, we need public properties for country and city, which we bind to inputs using wire:model directive.

app/Livewire/Dropdowns.php:

class Dropdowns extends Component
{
public Collection $countries;
public Collection $cities;
 
public int $country;
public int $city;
 
public function mount(): void
{
$this->countries = Country::pluck('name', 'id');
$this->cities = collect();
}
 
// ...
}

All that is left is to update cities when country public property is updated. We can use Lifecycle Hook updated.

app/Livewire/Dropdowns.php:

use App\Models\City;
 
class Dropdowns extends Component
{
public Collection $countries;
public Collection $cities;
 
public int $country;
public int $city;
 
public function mount(): void
{
$this->countries = Country::pluck('name', 'id');
$this->cities = collect();
}
 
public function updatedCountry($value): void
{
$this->cities = City::where('country_id', $value)->get();
$this->city = $this->cities->first()->id;
}
 
// ..
}

When a user selects a country, the lifecycle hook updatedCountry gets triggered. In this method, we set the cities list and the city property to the first from the cities list.