Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

dev-idkwhoami's avatar

AlpineJS initialization problem

I'm having trouble using Livewire in combination with AlpineJS. I have a Livewire Component in form of a table. and a dropdown with actions using AlpineJS. So each row of data has one Alpine component.

Randomly the dropdown doesn't work or only works partially and I can't figure out why. I tried x-init on the component and it did work.

file-table.blade.php:

<div class="rounded-lg bg-white shadow">
    <div class="px-4 py-5 sm:p-6">
        <div class="flex items-center justify-between">
            ...
        </div>

        <table class="mt-6 w-full whitespace-nowrap text-left">
            <colgroup>
                <col class="w-4/12">
                <col class="w-1/12">
                <col class="w-3/12">
                @if(is_null(auth()->user()->tenant_id))
                    <col class="w-2/12">
                @endif
                <col class="hidden lg:w-2/12">
                <col class="w-full">
            </colgroup>
            <thead class="border-b border-slate-300 text-sm leading-6 text-gray-900">
            <tr>
                <th scope="col" class="py-2 pl-0 pr-4 font-semibold">
                    <div class="flex justify-between">
                        Filename
                        <x-table.table-order-icon wire:click="orderBy('filename')" :is-current="$orderBy === 'filename'" :ascending="$orderAsc"/>
                    </div>
                </th>
                <th scope="col" class="py-2 pl-0 pr-4 font-semibold">
                    <div class="flex justify-between">
                        Size
                        <x-table.table-order-icon wire:click="orderBy('size', true)" :is-current="$orderBy === 'size'" :ascending="$orderAsc"/>
                    </div>
                </th>
                <th scope="col" class="py-2 pl-0 pr-4 font-semibold">Owner</th>
                <th scope="col" class="hidden py-2 pl-0 font-semibold xl:table-cell">
                    <div class="xl:flex xl:justify-between">
                        Timestamp
                        <x-table.table-order-icon wire:click="orderBy('timestamp')" :is-current="$orderBy === 'timestamp'" :ascending="$orderAsc"/>
                    </div>
                </th>
                <th scope="col" class="py-2 pl-0 pr-4 font-semibold"></th>
            </tr>
            </thead>
            <tbody class="divide-y divide-slate-300/80">
            @foreach($all_files as $file)
                <tr>
                    <td class="py-2 pl-0 pr-3 text-sm font-medium text-gray-900">...</td>
                    <td class="px-3 py-2 pl-0 text-sm text-gray-500">...</td>
                    <td class="px-3 py-2 pl-0 text-sm text-gray-500">...</td>
                    <td class="px-3 py-2 pl-0 text-sm text-gray-500 hidden xl:table-cell">...</td>
                    <td class="py-2 pl-3 pr-4 text-right text-sm font-medium sm:pr-3">
                        <x-table.file-table-actions :file="$file"/>
                    </td>
                </tr>
            @endforeach
            </tbody>
        </table>
        <div class="mt-8">
            {{ $all_files->links() }}
        </div>
    </div>
</div>

FileTable.php:

class FileTable extends Component
{
    use WithPagination, WithSorting;

    protected array $queryMap = [
        'timestamp' => 'created_at',
        'filename' => 'name',
    ];

    protected $listeners = ['file-deleted' => '$refresh', 'file-restored' => '$refresh'];

    public function paginationView(): string
    {
        return 'components.table.pagination-links';
    }

    public function render(): View
    {
        return view('livewire.files.file-table', [
            'all_files' => File::where('name', 'like', "%$this->search%")
                ->withTrashed(is_null(auth()->user()->tenant_id))
                ->orderBy($this->orderField(), $this->orderDirection())
                ->paginate(10),
        ]);
    }
}

file-table-actions.blade.php

@props(['file'])
<div x-data="{actionsOpen: false}" wire:key="{{ $file->id }}" class="relative inline-block text-left">
    <div x-cloak>
        <button @click="actionsOpen = !actionsOpen" type="button"
                class="inline-flex w-full justify-center gap-x-1.5 rounded-md bg-white p-1 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
                id="menu-button" aria-expanded="true" aria-haspopup="true">
            <svg x-show="actionsOpen" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 text-gray-500">
                <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5"/>
            </svg>
            <svg x-show="!actionsOpen" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 text-gray-500">
                <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5"/>
            </svg>
        </button>
    </div>
    <div @click.away="actionsOpen = false"
         x-show="actionsOpen"
         id="actions_{{$file->id}}" style="display: none"
         class="absolute right-0 z-10 -mt-0.5 w-36 origin-top-right divide-y divide-gray-100 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none" role="menu"
         aria-orientation="vertical" aria-labelledby="menu-button" tabindex="-1">
        <div class="py-1" role="none">
            <a href="#edit-file" data-file-id="{{ $file->id }}" class="text-gray-700 group flex items-center px-4 py-2 text-sm hover:bg-indigo-400/30" role="menuitem" tabindex="-1" id="edit-file">
                ...
            </a>
            <a href="#dupe-file" data-file-id="{{ $file->id }}" class="text-gray-700 group flex items-center px-4 py-2 text-sm hover:bg-indigo-400/30" role="menuitem" tabindex="-1" id="dupe-file">
                ...
            </a>
        </div>
        @if(is_null($file->deleted_at))
            <div class="py-1" role="none">
                <a href="#delete-file" @click.prevent="$dispatch('file-delete', {fileId: '{{ $file->id }}'})"
                   class="text-gray-700 group flex items-center px-4 py-2 text-sm hover:bg-indigo-400/30"
                   role="menuitem" tabindex="-1" id="delete-file">
                    ...
                </a>
            </div>
        @else
            <div class="py-1" role="none">
                <a href="#restore-file" @click.prevent="$dispatch('file-restore', {fileId: '{{ $file->id }}'})"
                   class="text-gray-700 group flex items-center px-4 py-2 text-sm hover:bg-indigo-400/30"
                   role="menuitem" tabindex="-1" id="restore-file">
                    ...
                </a>
            </div>
        @endif
    </div>
</div>

I did try adding wire:ignore but that didn't help also would interfere with the fact the dropdown does need to re-render as well since the actions are dynamic.

So I hope somebody can help me find the problem in this ^^

0 likes
2 replies
dev-idkwhoami's avatar

Small update to this problem. I found out that the problem is replicatable by switching between pages of the table and the "incomplete" initialized Alpine component is always the first row.

This means something just can't figure out what.

Also the @click listener is working on the button it is updating the value of actionsOpen but it just wont update the visibility of the dropdown.

dev-idkwhoami's avatar
dev-idkwhoami
OP
Best Answer
Level 10

OK so after hours of research and just trial & error I found out that the reason this happened in my case was that the button to toggle the dropdown had the same value for the id attribute which seems to have caused confusion within Alpines components when Livewire replaced the DOM.

Im not 100% sure of the reason but in case you encounter a similar problem check EVERY single id attribute for duplicates.

Please or to participate in this conversation.