On laravel 11 site I use on the form volt components defined as resources/views/livewire/common/controls/input_text.blade.php :
<?php
use Livewire\Volt\Component;
use Livewire\Attributes\Modelable;
new class extends Component {
#[Modelable]
public $form;
public string $formattedValue = '';
public string $fieldName = '';
public string $label = '';
public ?string $placeholder = '';
public ?int $maxlength = 0;
public ?int $tabindex = 0;
};
?>
<div class="editor_field_block_wrapper d2">
<div class="">
<div class="w-4/12 pb-0 pl-2 md:pt-3 ">
<label for="{{ $fieldName }}" class="">
{{ !empty($label) ? $label: \Str::ucfirst(\Str::replace('_', ' ', $fieldName)) }}:
</label>
</div>
<div class="p-2 w-full">
<input id="{{ $fieldName }}" name="{{ $fieldName }}" type="text"
class="editor_form_input"
@if(!empty($maxlength)) maxlength="{{ $maxlength }}" @endif
wire:model="form.{{ $fieldName }}"
autocomplete="off"
@if(!empty($placeholder)) placeholder="{{ $placeholder }}" @endif
@if(!empty($tabindex)) tabindex="{{ $tabindex }}" @endif/>
@error('form.' . $fieldName)
<span class="error_text">{{$message}}</span>
@enderror
</div>
</div>
</div>
and resources/views/livewire/common/controls/input_readonly.blade.php (I tried to use :key= in any "div" element like I read at https://livewire.laravel.com/docs/troubleshooting#adding-wirekey ):
<?php
use Livewire\Volt\Component;
use function Livewire\Volt\{mount, state};
state(['fieldName'=> '', 'form'=> '']);
new class extends Component {
public $form = [];
public $fieldName = '';
} ?>
<div class="editor_field_block_wrapper d1" :key="$fieldName . 'RootDivReadonly'">
<div class="" :key="$fieldName . 'SplitterDivReadonly'">
<div class="w-4/12 pb-0 pl-2 md:pt-3">
<label for="{{ $fieldName }}" class="">
{{ \Str::ucfirst($fieldName) }}:
</label>
</div>
<div class="p-2 w-full" :key="$fieldName . 'ParentDivReadonly'">
<input
id="{{ $fieldName }}" name="{{ $fieldName }}" type="text"
value="{{ $form->{$fieldName} }}"
tabindex="-1"
:key="$fieldName . 'Readonly'"
readonly/>
</div>
</div>
</div>
on the form I se both type controls :
<form class="form-editor" wire:submit="update" enctype="multipart/form-data">
<livewire:common.controls.input_readonly fieldName="id" :form="$form" :key="'idReadonly'" />
<livewire:common.controls.input_text fieldName="phone" wire:model="form" :maxlength="100" tabindex="10" :key="'phoneInputText'" />
<livewire:common.controls.input_text fieldName="website" wire:model="form" :maxlength="50" tabindex="20" placeholder="Fill your website url" :key="'websiteInputText'" />
</form>
Problem is that after I edit data in input_text elements and save the form I got error :
in Uncaught Snapshot missing on Livewire component with id: 2MfhLF0gKp6I1w1LtNDh
and my form is broken.
If I remove line with
<livewire:common.controls.input_readonly
component (it is the only on the form) - I have no this error and form is saved ok.
What is wrong in my code and how to fix it ?
"laravel/framework": "^11.9",
"livewire/livewire": "^3.5",
"livewire/volt": "^1.6",
UPDATES :
I remade resources/views/livewire/common/controls/input_readonly.blade.php file :
<?php
use function Livewire\Volt\{state};
state(['fieldName'=> '', 'form'=> '']);
?>
<div wire:key="$fieldName . 'RootDivReadonly'">
<div wire:key="$fieldName . 'SplitterDivReadonly'">
<div class="w-4/12 pb-0 pl-2 md:pt-3">
<label for="{{ $fieldName }}" >
{{ \Str::ucfirst($fieldName) }}:
</label>
</div>
<div class="p-2 w-full" wire:key="$fieldName . 'ParentDivReadonly'">
<input
id="{{ $fieldName }}" name="{{ $fieldName }}" type="text"
value="{{ $form->{$fieldName} }}"
tabindex="-1"
wire:key="$fieldName . 'Readonly'"
readonly/>
</div>
</div>
</div>
But still got the same error when I save the form. No error on page opened.
Did I define wire:key
correctly ?
Please provide a link to functional API manuals...
UPDATES 2:
Here https://livewire.laravel.com/docs/alpine#refreshing-a-component I found :
You can easily refresh a Livewire component (trigger network roundtrip to re-render a component's Blade view) using $wire.$refresh():
I try to use it as in my blade file I have :
<form class="form-editor" wire:submit="update" enctype="multipart/form-data">
<livewire:common.controls.input_readonly fieldName="id" :form="$form" :key="'idReadonly'" />
<livewire:common.controls.input_text fieldName="phone" wire:model="form" :maxlength="100" tabindex="10" :key="'phoneInputText'" />
<livewire:common.controls.input_text fieldName="website" wire:model="form" :maxlength="50" tabindex="20" placeholder="Fill your website url" :key="'websiteInputText'" />
</form>
I tried to use $refresh method in the parent livewire component :
use Livewire\Component;
class ProfileEditor extends Component
{
public function update(Request $request)
{
try {
$user = User::findOrFail($this->form->id);
} catch (ModelNotFoundException $e) {
return;
}
$user->phone = $this->form->phone;
$user->website = $this->form->website;
...
try {
DB::beginTransaction();
$user->save();
DB::commit();
$this->refresh(); // I added this method.
But I got runtime error :
Run time error :
Method App\Livewire\Personal\ProfileEditor::refresh does not exist.
How to use this method, as in blade file the form is submitted with " wire:submit="update" instruction ?
UPDATES 3:
I try to use both ways :
In component ProfileEditor I dispatched the event after data are saved:
$user->save();
$this->dispatch('profileEditorSaved', ['id' => $this->form->id]);
and in blade file I catch this event :
<div>
...
<form class="form-editor" wire:submit="update" enctype="multipart/form-data">
<livewire:common.controls.input_readonly fieldName="id" :form="$form" :key="'idReadonly'" />
<livewire:common.controls.input_text fieldName="phone" wire:model="form" :maxlength="100" tabindex="10" :key="'phoneInputText'" />
<livewire:common.controls.input_text fieldName="website" wire:model="form" :maxlength="50" tabindex="20" placeholder="Fill your website url" :key="'websiteInputText'" />
...
</form>
@script
<script>
// This event listener updates the component with all childs
$wire.on("profileEditorSaved", ($options) => {
console.log('profileEditorSaved $options::')
console.log($options)
$wire.$refresh();
});
</script>
@endscript
</div>
After data are saved I see in the browser's console output of profileEditorSaved event, but next the same error :
Uncaught Snapshot missing on Livewire component with id: VNoy1hE2mxNE9ewZ4T5O
Not shure how to implement it. I tried to modify input_readonly.blade.php as :
use function Livewire\Volt{state};
state(['fieldName'=> '', 'form'=> '']);
<div class="editor_field_block_device_splitter" wire:key="SplitterDivReadonly-{{rand()}}">
<div class="w-4/12 pb-0 pl-2 md:pt-3">
<label for="{{ $fieldName }}" class="editor_field_block_device_label">
{{ \Str::ucfirst($fieldName) }}:
</label>
</div>
<div class="p-2 w-full" wire:key="ParentDivReadonly-{{rand()}}">
<input
id="{{ $fieldName }}" name="{{ $fieldName }}" type="text"
class="editor_form_readonly"
value="{{ $form->{$fieldName} }}"
tabindex="-1"
wire:key="Readonly-{{rand()}}"
readonly/>
</div>
</div>
But still the same error!
Did I understand you correctly!
Thanks in advance!
I think the problem might be related to mixed Volt API. To declare variable you should either use functional API:
state(['fieldName'=> '', 'form'=> '']);
Or class API:
new class extends Component {
public $form = [];
public $fieldName = '';
}
Do not mix this, because it's completely different approaches. Functional API creates class under the hood, while with traditional class-based components you declare it explicit.
Also, you shouldn't use :key, but wire:key with html elements. For example
<livewire:some-component :key="unique-component-key"/>
<div wire:key="unique-key"></div>
Update. I checked your demo app. When I removed all wire:key attributes and refresh event listener, the issue is gone.
<?php
use function Livewire\Volt\{state};
state(['fieldName' => '', 'form' => '']);
?>
<div class="">
<div class="">
<div class="w-4/12 pb-0 pl-2 md:pt-3">
<label for="{{ $fieldName }}" class="">
{{ \Str::ucfirst($fieldName) }}:
</label>
</div>
<div class="p-2 w-full">
<input
id="{{ $fieldName }}" name="{{ $fieldName }}" type="text"
style="border:1px dotted"
value="{{ $form->{$fieldName} }}"
tabindex="-1"
readonly
/>
</div>
</div>
</div>
You can also remove dispatching event from your component.
$this->dispatch('saved');