I created a custom multiple select blade component and implemented alpinejs. It works perfectly fine on a full-page livewire component. When I include it on a modal that is available to each page and is included in the layouts page, I lose some reactivity with the custom multiple select.
The Livewire component has a public property $selected that is set by default to []. When a “editCustomer” event is raised, the component listener populates the value and then opens the modal.
Here’s what I have for the blade component:
@props(['options' => []])
<div x-data="tags({selected: @entangle($attributes->wire('model')), options: {{ $options }}})" class="relative bg-white overflow-y-visible z-10" @click.away="open = false">
<div class="border-2 border-gray-200 flex flex-wrap p-1 rounded-md text-sm">
<template x-for="(item, index) in getSelected()" :key="index">
<div class="flex border-2 rounded-full py-1 px-2 m-1 bg-gray-100">
<div class="flex items-center">
<span x-text="item[labelField]"></span>
<button class="focus:outline-none" x-on:click="removeItem(item[valueField])">
<svg class="h-4 w-4 ml-1 cursor-pointer" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</button>
</div>
</div>
</template>
<input x-on:click="toggleOpen()" class="ml-2 outline-none h-10" x-model="tag" type="text"/>
</div>
<ul x-show="open" class="absolute w-full overflow-auto bg-white mt-1 p-1 border-2 border-gray-200 rounded-md space-y-1">
<template x-for="option in options" :key="option[valueField]">
<li class="px-2 py-1 hover:bg-indigo-500 hover:text-white cursor-pointer rounded-md"
x-bind:class="isSelected(option[valueField]) ? 'bg-indigo-200' : ''"
x-on:click="toggleSelected(option[valueField])"
>
<span x-text="option[labelField]"></span>
</li>
</template>
</ul>
</div>
@push('scripts')
<script>
function tags(config) {
return {
open: false,
tag: '',
selected: config.selected ?? [],
options: config.options ?? [],
valueField: 'id',
labelField: 'name',
toggleOpen() {
this.open = ! this.open;
},
getSelected()
{
return this.options.filter(option => {
return this.selected.includes(option[this.valueField]);
});
},
isSelected(value) {
return this.selected.includes(value);
},
toggleSelected(value) {
if (this.selected.includes(value)) {
this.selected = this.selected.filter(sel => {
return sel !== value;
})
} else {
this.selected = this.selected.concat([value]);
}
},
removeItem(value) {
this.selected = this.selected.filter(sel => {
return sel !== value;
})
}
}
}
</script>
@endpush
The implementation is simple:
<x-input.multiple-select wire:model.defer="selected" :options="$contacts" />
I have tried removing the “defer” modifier and it does improve but requires 2 clicks of the button rather than just one.
My issue is that when I use in a modal, the selected values are present but I am unable to remove an existing option from the UI by clicking the button to removeItem, although wire:model is updated. However, clicking the <li>
removes the items from the UI as expected.
Any item I add from the UI can be removed by clicking the removeItem button but values set from the database cannot. This seems to be a reactivity issue where alpine isn’t aware of the items.
Here’s a short video: https://magnigen-public.s3.amazonaws.com/livewire_alpinejs_reactivity_issue.mp4
Any help is appreciated.