Relationship still being saved even when `->dehydrated(false)`
Package
filament/filament
Package Version
v4.0.12
Laravel Version
v12.28.1
Livewire Version
v3.6.4
PHP Version
8.3.25
Problem description
I am trying to convert some forms to use the new visibleJs()/hiddenJs() methods. One simple form example is a ToggleButtons field that selects an enum, and then a Select field that only is shown for one of the toggle button options.
Previously, I just used a ->live() and a ->visible(fn (Get $get) => $get('type') === ...) which worked fine, meaning that the second customer_id field would be dehydrated as null in the database. Even, in the situation that a user first selects a toggle button option that has the Select visible, the user selects a value from the Select and then the user changes the toggle button again so that the Select is hidden. In that case, even if the Select has a state selected, as it is not visible it was still dehydrated/saved as null.
Now, I converted this setup to a visibleJs(). This works ok, but I noticed that when the Select has a state and is then made hidden again, the value is still being dehydrated. That is then understandable, as the Filament backend has no way to know whether a field is hidden or visible in JS. To solve that then, I wanted to apply a ->dehydrated(fn () => false) closure, that would tell Filament to not dehydrate the field.
However, Filament still dehydrates the field. I suspect that is because of the ->relationship()-call. In my opinion, I think these relationships should respect the ->dehydrated() as well, because otherwise in the below example I do not see any way to convert this to the ->visibleJs() method.
ToggleButtons::make('type')
->options(DocumentType::class)
->inline()
->required(),
Select::make('customer_id')
->relationship('customer', 'name')
->searchable()
->preload()
->visibleJs(function () {
$documentTypeCustomer = DocumentType::Customer;
return <<<JS
\$get('type') === '{$documentTypeCustomer->value}'
JS;
})
->dehydrated(function (Get $get) {
$type = $get('type');
ray($type);
$documentTypeCustomer = DocumentType::Customer;
$isDehydrated = $type === $documentTypeCustomer;
ray('Dehydrated customer id: ' . $isDehydrated ? 'true' : 'false');
return $isDehydrated;
})
->requiredUnless('type', DocumentType::Customer->value),
There are two methods like ->saveRelationshipsWhenDisabled() and ->saveRelationshipsWhenHidden(). But I think I am of the opinion that a relationship should not be saved if the field is not dehydrated.
Expected behavior
See above.
Steps to reproduce
- Go to Documents > Create.
- Select the value "Customer-specific document"
- Select a customer
- Then switch back to type "Global document".
- Then save the document.
I expect the customer_id field to be null in the database.
Reproduction repository (issue will be closed if this is not valid)
https://github.com/ralphjsmit/demo/tree/rjs/bug-report-dehydrate
Relevant log output
But I think I am of the opinion that a relationship should not be saved if the field is not dehydrated.
Fields like repeater which have a relationship are not dehydrated either, yet their relationships still need saving.
I think you need ->dehydrated(false)->saveRelationshipsUsing(null)
I think I'll do a specific check just for select where if it is a singular select, it will not save the relationships when not dehydrated
Uh, thinking about it more, I'm not going to do that. It would be unexpected behaviour, since saving the relationships is still a distinct process to dehydration.
Maybe we just need an easier way to handle "is this component saved or not"
I think the BelongsTo relationship is a bit a special one as that one is acting "in essence" like any other direct model-field (though of course strictly it is still a relationship) and it not really saved by ->saveRelationshipsUsing().
If you have a method suggestion, then let me know and I make a PR.
The reason we still use saveRelationshipsUsing() for BelongsTo is that some people don't have it in their $fillable array
Another alternative I tested was to provide both a ->visible() and a ->visibleJs() simultaneously (with the idea that it would work dynamically in frontend JS and on the backend it would also be able to determine the state). However, then the frontend version was ignored iirc and only the backend version was used. It could be an option as well to consider making these supported simultaneously.