javascriptlaravel-livewireevent-listenerlaravel-11livewire-3

How to run a JavaScript method after livewire component update and re-rendering?


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.


Solution

  • 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>