Updating Livewire data from JavaScript
Updating the value of Livewire form inputs, e.g. Filament's Forms\Components\Textarea
component, using vanilla JavaScript DOM APIs can lead to issues during form submission, because such changes are not captured in the Livewire state.
Ideally, you should be using Alpine with Livewire and prevent this kind of scenario. However, we will consider a situation where using Alpine is not an option, and you need to update Livewire state, like in the Filament Textarea example described above.
Note
The appropriate way of handling this in Filament is to use
dehydrateStateUsing()
, as demonstrated later below.
How Filament/Livewire Manages State
Filament offers powerful tools for building dynamic form experiences. However, managing state when vanilla JavaScript DOM APIs modify inputs can be tricky. This is because Livewire—which Filament uses under the hood—tracks input values using an internal model that syncs changes that are as a result of user actions, and not via direct JavaScript DOM manipulations.
Here’s a simple example to illustrate the issue:
use Filament\Forms; // In your form builder $builder = Forms\Form::make() ->schema([ Forms\Components\Select::make('title') ->options(['mr' => 'Mr', 'mrs' => 'Mrs', 'prof' => 'Prof']) ->extraAttributes(['onchange' => 'updateFullName()']), Forms\Components\TextInput::make('firstname'), ]); // JavaScript function function updateFullName() { const firstname Input = document.getElementById('firstname'); const titleSelect = document.getElementById('title'); if (!firstnameInput || !titleSelect) return; let fullName = `${titleSelect.options[titleSelect.selectedIndex].text} ${firstnameInput.value}`; firstnameInput.value = fullName; // Append the selected title to the firstname }
From the code above, notice that when a title is selected, JavaScript appends it to the user’s first name in the input field. This reflects in the browser and the user sees this change. However, this change is not reflected in Livewire's internal state.
Suggested Approaches
Approach 1: Notify Livewire explicitly of the changes using Livewire.find(…).set
.
function updateFullName() { const firstname Input = document.getElementById('firstname'); const titleSelect = document.getElementById('title'); if (!firstnameInput || !titleSelect) return; let fullName = `${titleSelect.options[titleSelect.selectedIndex].text} ${firstnameInput.value}`; firstnameInput.value = fullName; // Notify Livewire of the change Livewire.find(document.querySelector('[wire\\:id]').getAttribute('wire:id')).set('firstname', fullName); }
Approach 2: Use Filament's built-in mechanism, such as the dehydrateStateUsing
API, directly on the component itself. This is the appropriate and recommended approach to this issue.
use Filament\Forms; // In your form builder $builder = Forms\Form::make() ->schema([ Forms\Components\Select::make('title') ->options(['mr' => 'Mr', 'mrs' => 'Mrs', 'prof' => 'Prof']), Forms\Components\TextInput::make('firstname') ->dehydrateStateUsing(function (string $state, Forms\Get $get): string { return $get('title') . ' ' . $state; }), ]);
Conclusion
By understanding and managing state changes effectively in Filament, you can avoid common pitfalls like ignoring JavaScript-driven changes or relying solely on JavaScript for state management. Leverage Filament APIs such as dehydrateStateUsing
for better state management. This approach ensures accurate data tracking and simplifies your development process.