In just a few steps, deploy your Laravel application on Fly.io, you’ll be up and running in minutes!
How do we create dependency among dropdowns that use the same, re-usable component? One way we can solve this is by utilizing Livewire’s powerful component nesting capabilities.
Let’s check it out, here’s a repository for reference.
Problem
We have a re-usable filter
component that contains a select dropdown:
<!-- resources/views/livewire/filter.blade.php -->
<div>
<!-- Show the label of the filter -->
<label>{{ $label }}</label>
<!-- Filter -->
<select wire:model="value">
@foreach( $options as $option )
<option value="{{ $option['id'] }}">{{ $option['name'] }}</option>
@endforeach
</select>
</div>
We want to use this to create 3 different dropdown filters ( Offer Type, Property Type, and Amenities ) for our page.
One key point is that changes on the Property Type dropdown should filter the options in the Amenities dropdown.
How do we create such dependency that come from the same component?
Solution: A Parent Data Manager
At the end of the day, what we really have here is data dependency: Data from one filter
component reacting to data from another filter
component.
And in the beginning, where did we even get data to instantiate these two components? —Somewhere outside of it.
So, if we get data from outside of the component, why not handle data stuff like dependency outside of the component as well?
We can create a filter-section
component that will manage the dropdowns. It will be responsible for managing data, rendering, and coordinating events across its filter
child components.
And, thanks to Livewire v3’s component nesting capabilities, all these data and event coordination, as well as re-rendering of UI changes, is going to be smooth.
Set Up
First, create a filter
component, it’s the “template” to our dropdowns.
Afterwards, create another component called filter-section
, this will act as the parent manager.
Getting Data
The filter-section
will have a public attribute, $filters
, that will contain data on the three dropdowns we want to render in our page:
/* app/Livewire/FilterSection.php */
public $filters = [];
public function mount()
{
$anyArray = ['id'=>0,'name'=>'Any'];
$this->filters = [
'offer_type'=>[
'value' => 0,
'label'=> 'OFFER TYPE',
'options'=> [ $anyArray ]+OfferType::all()->toArray()
],
'property_type'=>[
'value' => 0,
'label'=>'PROPERTY TYPE',
'options'=>[ $anyArray ]+PropertyType::all()->toArray()
],
'amenity'=>[
'value' => 0,
'label'=>'AMENITIES',
'options'=>[ $anyArray ]+Amenity::all()->toArray()
]
];
}
Initializing Child Components
In its view, we’ll instantiate a bunch of child filter
components using the items in $filters
. For each item, we initialize a filter
component with the item’s $label
, $options
, and $value
:
<!-- resources/livewire/filter-section.blade.php -->
<div>
@foreach( $filters as $key=> $filter )
<livewire:filters
:key="$key.now()"
:label="$filter['label']"
:options="$filter['options']"
:value="$filter['value']"
/>
@endforeach
</div>
Saving the code snippet above should give us an instance of filter
component for each item in the $filters
array:
Next, let’s focus on the :key
attribute declared above.
The Importance in :key
Including a :key
identifier in each filter
child component is a vital, mandatory requirement. Livewire uses this :key
attribute to keep track of children components during re-rendering of changes in the page.
And, speaking of “re-rendering”, Livewire by default only renders a child component when the child component has not yet been rendered before.
In fact, a parent component maintains this list called “children”. It contains the :key of all child components previously rendered. Whenever the parent needs to re-render, it skips any child whose key has already been recorded in the list.
Since our “Amenities” dropdown has already been rendered in initial page load, Livewire’s not going to re-render it afterwards. But see, we need re-rendering for changes to reflect, so what do we do?
This, is where $key.now()
comes in. This should generate a unique key for our dropdown every time, allowing it to have a different key from before, forcing the re-rendering of the component—and reflecting new changes to our dropdowns ✨
Dependent Filters
Now, let’s go over to the “dependency” part.
When a user selects a “Property Type” option, we want to filter the “Amenities” options to only include options for that specific property type.
There’re two parts to this: Listening for “Property Type” changes, and Requesting the parent component to filter its “Amenities” dropdown. Add this logic to the filter
view:
<!-- resources/views/livewire/filter.blade.php -->
<select
x-on:change="
$wire.label == 'PROPERTY TYPE' &&
$wire.$parent.filterAmenity( $wire.value )"
>
What’s happening above?
First, notice we use $wire
everywhere. It’s this magical JavaScript object that represents the current component that caused the action.
Then, for the bigger picture we make use of some Alpine. We combine a quick x:on-change to listen for option changes, and add in a conditional statement to specifically listen to a component that has $wire.label
“PROPERTY TYPE”.
Finally, we proceed with calling the parent’s method filterAmenity()
to filter the data for the “Amenities” dropdown using the argument passed.
Filtering with PropertyTypeId
Lets wrap this up by declaring the parent’s filterAmenity()
method called from above. We declare it as a public method in our filter-section
component’s class:
/* app/Livewire/FilterSection.php */
public function filterAmenity( $propertyTypeId )
{
$this->filters['amenity'] = [
'value'=>0,
'label'=>'AMENITIES',
'options'=>Amenity::filterPropertyType( $propertyTypeId )
->get()->toArray()
];
}
The function simply receives a $propertyTypeId, and uses it to update the options of the amenities filter item. Save the changes, and see our dropdowns in action!
The Takeaway
When introducing dependency across dropdowns that use the same, re-usable component, there are three general questions that need to be answered:
- How can we listen to change from a specific dropdown?
- How can we communicate this change to another specific dropdown?
- How can this change be reflected in the UI?
The solution we used today relied on a data manager component to communicate dependency among our dropdowns. And with Livewire, we were able to easily implement this solution.
We combined Livewire’s in built Alpine x-on:change
, and $wire
object to listen for changes from a specific dropdown ( answering Q#1 ). Finally, we accessed the $parent object from our child component to call its parent’s method to filter the data for the dependent “Amenities” dropdown( answering Q#2! ).
With Livewire, we didn’t even have to worry about the third question! We just had to provide the proper always-unique :key
value and Livewire handled everything for our Amenities dropdown to reflect its brand new, filtered options.
All this in a few lines of code. Amazing