I want to run a javascript method called makeFinalAdjustments()
, but it needs to happen after the page has finished re-rendering the content following a click event inside this livewire component:
//CHILD LIVEWIRE COMPONENT
//Child.blade.php
<div>
<div wire:click="updateContent($id)">Click here</div>
<div>{{ $content }}</div>
....
</div>
//Child.php
public $content;
public $id;
public function updateContent()
{
$this->dispatch('updateContent');
}
//PARENT LIVEWIRE COMPONENT
//Parent.php
protected $listeners = [
'updateContent' => 'handleUpdateContent',
];
public function handleUpdateContent($child_id): void
{
$this->content[$child_id] = $this->getSomeContent($child_id);
}
//parent.blade.php
<div>
@foreach(child in list)
<livewire:child :content=content[child->id]>
@endforeach
...
</div>
I've tried adding an eventlistener based on the documentation here....
document.addEventListener('livewire:init', function () {
Livewire.on('updateContent', () => {
Livewire.hook('commit', ({ component, commit, respond, succeed, fail }) => {
succeed(({ snapshot, effect }) => {
queueMicrotask(() => {
makeFinalAdjustments();
})
})
})
});
});
...but it doesn't work because makeFinalAdjustments()
fires too early, before the new content has finished rendering.
The problem is not completely clear, we should understand what is executed in the makeFinalAdjustments() function.
In any case, I tried to emulate the situation, reconstructing the incomplete parts of the request.
Parent class
class ParentTest extends Component
{
public array $content = [];
public array $children = [];
protected $listeners = [
'updateContent' => 'handleUpdateContent',
];
public function mount() {
$this->children = [
(object)['id' => 1],
(object)['id' => 2],
(object)['id' => 3],
];
$this->content = [
1 => Str::random(),
2 => Str::random(),
3 => Str::random()
];
}
public function handleUpdateContent($child_id): void
{
// for this test we use a value of convenience
// $this->content[$child_id] = $this->getSomeContent($child_id);
$this->content[$child_id] = Str::random();
}
}
Parent view
<div>
@foreach($children as $child)
<livewire:child-test :content="$content[$child->id]"
:id="$child->id"
:key="$child->id"
>
<br>
<hr>
<br>
@endforeach
</div>
Child class
namespace App\Livewire;
use Livewire\Attributes\Reactive;
use Livewire\Component;
class ChildTest extends Component
{
public $id;
#[Reactive]
public $content;
// --- since the event is now dispatched from the frontend this is no longer needed
//
// public function updateContent($id)
// {
// $this->dispatch('updateContent', child_id: $id);
// }
}
Child view
<div>
{{--<div wire:click="updateContent({{ $id }})">--}}
<div wire:click="$dispatch('updateContent', {child_id: {{ $id }} })"> {{-- <~~~ now the event is dispatched from the frontend --}}
[Click here]
</div>
<div x-init="$watch('$wire.content', () => $nextTick(() => makeFinalAdjustments({{ $id }})))">
<span id="content-{{ $id }}">
{{ $content }}
</span>
<span id="revContent-{{ $id }}">
</span>
</div>
</div>
<script>
function makeFinalAdjustments(id)
{
const content = document.getElementById(`content-${id}`).textContent;
const revContent = content.split("").reverse().join("");
document.getElementById(`revContent-${id}`).textContent = revContent;
}
</script>
In the parent class I emulated the initial content of the public variables using some random strings for the content
In the handleUpdateContent() event handler I replaced getSomeContent() with a new random string
In the parent view I've provided the content and the id parameters, I've also added an unique key
The $content property of the child class has the attribute #[Reactive], so the change in the parent is reflected to the related child
I dispatched the child-component event from the view, so I saved a call to the backend, and the child's updateContent() method is no longer needed
In the child view I've added a $watch over the $wire.content variable using Alpine, so when it changes, at the $nextTick the makeFinalAdjustments() function is called.
the makeFinalAdjustments() function in this test reads the content from the DOM and updates the revContent element with a reversed version of the original: as you can see, using $nextTick the makeFinalAdjustments() is called when the update of the component is completed on the Livewire side. You will see that Livewire restores all the revContent contents as empty, but then the revContent related to the clicked button is filled