Set State of Select Field After Dynamically Populating Via Dependent Field

Hey all, I've been trying to solve this for two days and I'm sure it's something simple that I'm missing but I keep hitting a wall.
Using the code below, I'm populating the "service_call_id" field using an eloquent query based on the "customer_id" field.
However, once the "service_call_id" options have been populated, I'd like to automatically set the state of the "service_call_id" field to the very first result returned from the query. I've tried using default, selectablePlaceholder(false), and manually manipulating the state but I can't seem to find a way to hook into an event that happens after the options are populated. Any help would be GREATLY appreciated:
Forms\Components\Select::make('customer_id')
->relationship('customer', 'email')
->live(onBlur: true)
->searchable()
->required(),
Forms\Components\Select::make('service_call_id')
->options(function (Get $get) {
$serviceCalls = ServiceCall::query()
->whereRelation('workOrder.customer', 'id', $get('customer_id'))
->orderBy('id', 'desc')
->pluck('id', 'id');
return $serviceCalls;
})
->live()
->required(),
Forms\Components\Select::make('customer_id')
->relationship('customer', 'email')
->live(onBlur: true)
->searchable()
->required(),
Forms\Components\Select::make('service_call_id')
->options(function (Get $get) {
$serviceCalls = ServiceCall::query()
->whereRelation('workOrder.customer', 'id', $get('customer_id'))
->orderBy('id', 'desc')
->pluck('id', 'id');
return $serviceCalls;
})
->live()
->required(),
16 Replies
MohamedSabil83
MohamedSabil833mo ago
You can do similar to the following:
Forms\Components\Select::make('service_call_id')
->options(function (Get $get) {
$serviceCalls = ServiceCall::query()
->whereRelation('workOrder.customer', 'id', $get('customer_id'))
->orderBy('id', 'desc')
->pluck('id', 'id');
return $serviceCalls;
})
->afterStateHydrated(function ($state, Forms\Components\Select $component, Forms\Set $set) {
if (filled($state)) {
return;
}
$set('service_call_id', array_key_first($component->getOptions()));
})
->live()
->required(),
Forms\Components\Select::make('service_call_id')
->options(function (Get $get) {
$serviceCalls = ServiceCall::query()
->whereRelation('workOrder.customer', 'id', $get('customer_id'))
->orderBy('id', 'desc')
->pluck('id', 'id');
return $serviceCalls;
})
->afterStateHydrated(function ($state, Forms\Components\Select $component, Forms\Set $set) {
if (filled($state)) {
return;
}
$set('service_call_id', array_key_first($component->getOptions()));
})
->live()
->required(),
bahamagician
bahamagician3mo ago
Hey, thanks, I just tried that but unfortunately it doesn't work. So it seems that if I do the following, the service_call_id state gets updated but the select field doesn't reflect the state change on the front end. It still says "Select an option". Is there a way to trigger the form field to update to reflect the state of that field?
Forms\Components\Select::make('customer_id')
->relationship('customer', 'email')
->live(onBlur: true)
->afterStateUpdated(function (Get $get, Set $set, Select $component) {
$options = $component->getContainer()->getComponent('service_call_select')->getOptions();
$set('service_call_id', array_key_first($options));
})
->searchable()
->required(),
Forms\Components\Select::make('service_call_id')
->options(fn (Get $get): Collection => ServiceCall::query()
->whereRelation('workOrder.customer', 'id', $get('customer_id'))
->orderBy('id', 'desc')
->pluck('id', 'id'))
->live()
->key('service_call_select')
->required(),
Forms\Components\Select::make('customer_id')
->relationship('customer', 'email')
->live(onBlur: true)
->afterStateUpdated(function (Get $get, Set $set, Select $component) {
$options = $component->getContainer()->getComponent('service_call_select')->getOptions();
$set('service_call_id', array_key_first($options));
})
->searchable()
->required(),
Forms\Components\Select::make('service_call_id')
->options(fn (Get $get): Collection => ServiceCall::query()
->whereRelation('workOrder.customer', 'id', $get('customer_id'))
->orderBy('id', 'desc')
->pluck('id', 'id'))
->live()
->key('service_call_select')
->required(),
MohamedSabil83
MohamedSabil833mo ago
Because you use ->key('service_call_select'), I guess you need to use that key instead of $set('service_call_id', array_key_first($options));
Povilas K
Povilas K3mo ago
@bahamagician I've been experimenting with a similar example and made it work from another angle: instead of afterStateUpdated() on the parent, you JUST set the values in the options() of the child. Also added selectablePlaceholder() with callback function condition. Look how it worked for me, transform it to your case:
Forms\Components\Select::make('country_id')
->live()
->label('Country')
->dehydrated(false)
->options(Country::pluck('name', 'id')),
Forms\Components\Select::make('city_id')
->required()
->label('City')
->placeholder(fn (Forms\Get $get): string => empty($get('country_id')) ? 'First select country' : 'Select an option')
->selectablePlaceholder(fn (Forms\Get $get): bool => empty($get('country_id')))
->options(function (?Shop $record, Forms\Get $get, Forms\Set $set) {
if (! is_null($record) && $get('country_id') === null) {
$set('country_id', $record->city->country_id);
}

$cities = City::where('country_id', $get('country_id'))->pluck('name', 'id');
$set('city_id', array_key_first($cities->toArray()));

return $cities;
}),
Forms\Components\Select::make('country_id')
->live()
->label('Country')
->dehydrated(false)
->options(Country::pluck('name', 'id')),
Forms\Components\Select::make('city_id')
->required()
->label('City')
->placeholder(fn (Forms\Get $get): string => empty($get('country_id')) ? 'First select country' : 'Select an option')
->selectablePlaceholder(fn (Forms\Get $get): bool => empty($get('country_id')))
->options(function (?Shop $record, Forms\Get $get, Forms\Set $set) {
if (! is_null($record) && $get('country_id') === null) {
$set('country_id', $record->city->country_id);
}

$cities = City::where('country_id', $get('country_id'))->pluck('name', 'id');
$set('city_id', array_key_first($cities->toArray()));

return $cities;
}),
toeknee
toeknee3mo ago
So this was similar to what I was trying to say with afterStateUpdated seems a better place to handle the setting to me. I wonder if your approach makes it faster however
Ozumat77
Ozumat773mo ago
I found this interesting on your Filament Daily and I tried to implement it. However, in edit mode, the value showed on the parent select is its id, not the name.
toeknee
toeknee3mo ago
Good point, you'll need to condition if $record->city_id then pluck where id = city_id
Povilas K
Povilas K3mo ago
The version on Filament Daily is not perfect, I found bugs there and that's why I returned to this topic to shoot the updated version. So above is the code I came up with, you're saying it doesn't work? Worked well for me just an hour ago, showed parent normally.
Ozumat77
Ozumat773mo ago
Yeah, it doesn't. Exactly the same code written above
No description
Ozumat77
Ozumat773mo ago
Of course I think so
Povilas K
Povilas K3mo ago
Mmmmkay will double-check when back to computer, thanks
Ozumat77
Ozumat773mo ago
Pleasure is mine
LeandroFerreira
LeandroFerreira3mo ago
Could you try this please?
Forms\Components\Select::make('customer_id')
->relationship('customer', 'email')
->required()
->searchable()
->live()
->afterStateUpdated(callback: function ($state, Select $component) {
$select = $component->getContainer()->getComponent('service_call');
$select->state(array_key_first($select->getOptions()));
}),
Forms\Components\Select::make('service_call_id')
->key('service_call')
->options(fn(Get $get) => ServiceCall::whereRelation('workOrder.customer', 'id', $get('customer_id'))
->pluck('id','id')
->toArray())
->extraInputAttributes(['wire:key' => Str::random(10)])
Forms\Components\Select::make('customer_id')
->relationship('customer', 'email')
->required()
->searchable()
->live()
->afterStateUpdated(callback: function ($state, Select $component) {
$select = $component->getContainer()->getComponent('service_call');
$select->state(array_key_first($select->getOptions()));
}),
Forms\Components\Select::make('service_call_id')
->key('service_call')
->options(fn(Get $get) => ServiceCall::whereRelation('workOrder.customer', 'id', $get('customer_id'))
->pluck('id','id')
->toArray())
->extraInputAttributes(['wire:key' => Str::random(10)])
bahamagician
bahamagician3mo ago
This did it, thanks! Is it okay to leave the random function in there for the wire:key? Is that going to cause any interference with other components?
LeandroFerreira
LeandroFerreira3mo ago
This trick has been working for me when I need to make updates, like this https://github.com/livewire/livewire/discussions/1895#discussioncomment-139040 However, I haven’t tried it in this specific context. If anyone has a better approach, I’d be interested to hear it as well ✌️
GitHub
livewire nesting component only works once · livewire livewire · Di...
Im using livewire component add product to shopping cart and change their quantities, there are three components. product-quantity used as child components in table of cart component, when add prod...
Andrew Wallo
Andrew Wallo3mo ago
Using the trick you mentioned I found also the only way to make a date picker field reactively update. This was a long time ago though so I’m not sure if the date picker can currently be set reactively,