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.

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;
            }),
    ]);
Databases in VS Code? Get DevDb

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.

Wanna chat about what you just read, or anything at all? Click here to tweet at me on 𝕏