Search code examples
laravel-livewirealpine.js

Dispatching browser event with property alpine js and livewire


I'm trying to pass parameter when dispatching an event on browser using alpine and livewire

On the 1st attempt the result is what i was expected , id match with the property i've passed :

1st attempt

But on the 2nd attempt, weird things happened, the id didn't match with the property i've passed :

2nd attempt

The blade view where i passed the parameter :

<div>
    <div class="card">
        <div class="d-flex justify-content-between align-items-center card-header">
          Todo List
          <livewire:todos.todo-add />
        </div>
        <ul class="list-group list-group-flush">
            @forelse ($todos as $todo)    
                <li class="list-group-item d-flex justify-content-between">
                    {{ $todo->title }}
                    <div x-data>
                        <button
                            class="btn btn-success"
                            @click.prevent="$dispatch('toast-confirmation', {
                                id: {{ $todo->id }},
                                action: 'complete',
                                message: 'Are you sure to complete this task ?'
                            })">Complete</button>
                        <button
                            class="btn btn-danger"
                            @click.prevent="$dispatch('alert-confirmation', {
                                id: {{ $todo->id }},
                                action: 'delete',
                                message: 'Are you sure to delete this task ?'
                            })">Delete</button>
                    </div>
                </li>
            @empty
                No data.
            @endforelse
        </ul>
    </div>
    <div class="d-flex justify-content-end mt-3">
        {{ $todos->links() }}
    </div>

    <x-alerts />
    <x-toasters />
</div>

TodoIndex livewire class :

class TodoIndex extends Component
{
    use WithPagination;

    protected $paginationTheme = 'bootstrap';
    
    protected $listeners = [
        'todoAdded' => '$refresh',
        'complete',
        'delete',
    ];

    public function complete(Todo $todo)
    {
        $todo->update();

        $this->dispatchBrowserEvent('toast', [
            'type' => 'success',
            'message' => 'Task has been completed'
        ]);
    }
    
    public function delete(Todo $todo)
    {
        $todo->delete();

        $this->dispatchBrowserEvent('toast', [
            'type' => 'success',
            'message' => 'Task has been deleted'
        ]);
    }

    public function render()
    {
        return view('livewire.todos.todo-index', [
            'todos' => Todo::latest()->paginate(5)
        ]);
    }
}

alerts (laravel-component) :

<div>
    @push('alerts')
        <script>
            // Alert Confirmation
            window.addEventListener('alert-confirmation', event => {
                Swal.fire({
                    title: 'Are you sure to delete this id : ' + event.detail.id + '?',
                    text: "You won't be able to revert this!",
                    icon: 'warning',
                    showCancelButton: true,
                    confirmButtonColor: '#3085d6',
                    cancelButtonColor: '#d33',
                    confirmButtonText: 'Yes, delete it!'
                }).then((result) => {
                    if (result.isConfirmed) {
                        Livewire.emit(event.detail.action, event.detail.id)
                    }
                })
            })

            // Alert Info
            window.addEventListener('alert-info', event => {
                Swal.fire({
                    icon: event.detail.icon,
                    title: 'Oops...',
                    text: 'Something went wrong!'
                })
            })
        </script>
    @endpush
</div>

app.layouts (laravel component) :

<!doctype html>
<html lang="en">
    <head>
        <!-- Required meta tags -->
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <!-- Bootstrap CSS -->
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">

        <title>Hello, world!</title>

        <link rel="stylesheet" href="/css/style.css">
    
        @livewireStyles
        
        <script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.8.2/dist/alpine.min.js" defer></script>
    </head>
    <body>
        <x-navbar />
        
        <div class="container py-4">
            {{ $slot }}
        </div>


        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
        
        {{-- SweetAlert2 --}}
        <script src="//cdn.jsdelivr.net/npm/sweetalert2@11"></script>

        {{-- Toastr --}}
        <script src="/js/cute-alert/cute-alert.js"></script>
        
        @stack('alerts')

        @stack('toasters')

        @livewireScripts
    </body>
</html>

Any idea how to solve this ?


Solution

  • Got the answer from discord , Thanks to Mr. Josh Hansley And actually it also mentioned in the documentation as the Dom Diffing Issue

    In my case the solution is by adding the wire:key="todo-{{ $todo->id }} like so :

    <li class="list-group-item d-flex justify-content-between" wire:key="todo-{{ $todo->id }}">
    
        {{ $todo->title }}
    
        <div x-data>
    
            <button
                class="btn btn-success"
                @click.prevent="$dispatch('toast-confirmation', {
                    id: {{ $todo->id }},
                    action: 'complete',
                    message: 'Are you sure to complete this task ?'
                })">Complete</button>
            <button
                class="btn btn-danger"
                @click.prevent="$dispatch('alert-confirmation', {
                    id: {{ $todo->id }},
                    action: 'delete',
                    message: 'Are you sure to delete this task ?'
                })">Delete</button>
    
        </div>
    </li>
    

    add the "todo-" prefix is the best practice to avoid some weird morphdom errors to occur