Rafaelindriago's avatar

Livewire V3 does not wait until DOM changes are finished to dispatch events on JS side.

Hey guys! I'm very happy that Livewire 3 was released, with version 2 we used "emit" method to dispatch event to other components and listen on PHP side, and "dispatchBrowserEvent" method to dispatch event to JavaScript side, i remember that this last method, it waits until the DOM changes are made to run the listener of the event, but now on version 3, we only got "dispatch" method and to listen on JavaScript we can use normal listeners or listen with "Livewire.on" method, but with this way, the listener runs at the same time when the DOM changes are made, so if i need to manipulate the DOM is not safe, because it still the old DOM before the lastest update from PHP side, i don't know, maybe i'm confused and using the event as a wrong way, if anyone can help me with this, i'll really appreciate.

0 likes
2 replies
Rafaelindriago's avatar
Rafaelindriago
OP
Best Answer
Level 1

I found a solution using Javascript "hooks", this is the PHP class for my toast alertor component:

<?php

namespace App\Livewire\Components;

use Illuminate\Support\Arr;
use Illuminate\Support\Carbon;
use Illuminate\View\View;
use Livewire\Attributes\On;
use Livewire\Component;

class Alertor extends Component
{
    public array $alerts = [];

    public function render(): View
    {
        return view('livewire.components.alertor');
    }

    #[On('alertor:alert')]
    public function alert(string $type, string $message): void
    {
        $this->alerts[] = [
            'type'      => $type,
            'message'   => $message,
            'timestamp' => Carbon::now()->timestamp,
        ];
    }

    public function dismiss(int $key): void
    {
        Arr::forget($this->alerts, $key);
    }
}

This is the blade view of the component:

@php
    use Illuminate\Support\Carbon;
@endphp

<div class="toast-container position-fixed bottom-0 end-0 mb-2 me-2"
     @if (count($alerts) > 0) wire:poll.5000ms @endif>

    @foreach ($alerts as $key => $alert)
        <x-toast data-key="{{ $key }}"
                 wire:ignore.self=""
                 wire:key="alert-{{ $key }}">
            <x-toast.header>

                <strong class="me-auto">
                    @switch($alert['type'])
                        @case('success')
                            <span class="fas fa-fw fa-check-circle text-success"></span>
                            {{ __('Success') }}
                        @break

                        @case('error')
                            <span class="fas fa-fw fa-times-circle text-danger"></span>
                            {{ __('Error') }}
                        @break

                        @case('warning')
                            <span class="fas fa-fw fa-exclamation-circle text-warning"></span>
                            {{ __('Warning') }}
                        @break

                        @case('info')
                            <span class="fas fa-fw fa-info-circle text-info"></span>
                            {{ __('Notice') }}
                        @break

                        @default
                            <span class="fas fa-fw fa-info-circle text-info"></span>
                            {{ __('Notice') }}
                        @break
                    @endswitch
                </strong>

                <small class="me-1">
                    {{ Carbon::createFromTimestamp($alert['timestamp'])->diffForHumans(['options' => Carbon::JUST_NOW]) }}
                </small>
                <x-toast.close />
            </x-toast.header>

            <x-toast.body>
                {{ $alert['message'] }}
            </x-toast.body>
        </x-toast>
    @endforeach

</div>

@push('js')
    <script>
        document.addEventListener('livewire:initialized', () => {
            Livewire.hook('morph.added', (element) => {
                if (element.classList.contains('toast')) {
                    const toastObject = new bootstrap.Toast(element, {
                        delay: 60000
                    })

                    toastObject.show()
                }
            })

            document.querySelector('.toast-container')
                .addEventListener('hide.bs.toast', (event) => {
                    @this.call('dismiss', event.target.dataset.key)
                })
        })
    </script>
@endpush

Using the hook, the new toast alert element is already added when the listener runs, so i can safely initialize the toast object from Bootstrap framework, this hook is very powerfull, hope this can help other developers who loves Laravel and Liewire. 😎

3 likes
bkoruznjak's avatar

@The Raf Thanks for this. You helped one sad angry dev tonight and i would buy you a beer if i could :)

On a side note its really frustrating that livewire 3.0 removed the "afterDomUpdate" hook callback without documenting it anywhere on their page with a possible alternative. I ended up handling my HTML <-> Component communication like so:

pdp-screen.blade

.... bottom of my blade template file...
@push('screen-scripts')
    <script type="text/javascript">
        document.addEventListener('DOMContentLoaded', function () {
            Livewire.hook('morph.added', (element) => {
            @this.checkIfSwiperShouldBeRecreated()
            });

            Livewire.on('recreateSwiper', () => {
                //call a global js method that will re-init the swiper
                window.recreateSwiper();
            });
        });
    </script>
@endpush

PdpScreen.php

class ProductDetailsScreen extends Component
{
    public bool $shouldReInitSwiper = false;
    ...
    public function changeProductVariant():void
    {
        //fetch new product and update data in livewire component
        ...
        $this->shouldReInitSwiper = true;
    }
    ...
    public function checkIfSwiperShouldBeRecreated(): void
        {
            if ($this->shouldReInitSwiper) {
                $this->shouldReInitSwiper = false;
                $this->dispatch('recreateSwiper');
            }
        }
    ....
}

At the moment i don't know of a better way to do this and there is a lot of back and forth so if anyone has a better idea how to achieve the same result with Livewire 3.0 i am all ears

1 like

Please or to participate in this conversation.