Remove DOM element before Refresh using AlpineJs

Hello Everyone!

I have a form that allows iterative multiple select values. For the select field, I used select2 field with wire:ignore but because the select fields are iterative and multiple if I tied to remove one select field from the form I get either item removed from livewire property with all the under select fields being empty or removal of the select field while keeping unnecessary data on my livewire property.

here is a sample code:

<div>
    @dump($elements)

    @foreach ($elements as $element)
        <div
            x-data="{
                removeField() {
                    $($el).remove() // if this line and the next line swapped, I will get either of the above mentioned results
                    $wire.removeRaterField({{ $loop->index }})
                }
            }">
            <div class="form-group">
                <label for="rater_type-{{ $loop->index }}">Element #{{ $loop->index + 1 }}</label>
                <x-select-two-field name="elements[]" placeholder="Select Rater Type #{{ $loop->index + 1 }}" required :iterable="$iterable" wire:model="elements.{{ $loop->index }}" />
            </div>
    
            <div class="d-flex justify-content-end">
                <div class="btn-group" role="group">
                    @if ($loop->last)
                        <button type="button" class="btn btn-success btn-sm" wire:click="addRaterField">Add Rater</button>
                    @endif
                    @if (($loop->last && !$loop->first) || !$loop->last)
                        <button type="button" class="btn btn-danger btn-sm" @click="removeField()">Remove Rater</button>
                    @endif
                </div>
            </div>
        </div>
    @endforeach
</div>

I greatly appreciate if someone could help me with this one. Thanks in advance.

Wire Key on the loop?

Cures:

  • Add wire:key to elements inside loops:
<ul>
    @foreach ($items as $item)
        <li wire:key="{{ $loop->index }}">{{ $item }}</li>
    @endforeach
</ul>
  • Add key() to nested components in a loop
<ul>
    @foreach ($items as $item)
        @livewire('view-item', ['item' => $item], key($loop->index))

        <!-- key() using Laravel 7's tag syntax -->
        <livewire:view-item :item="$item" :key="$loop->index">
    @endforeach
</ul>
1 Like

@sheomoney Thank you so much you gave me an insight and with a few tweaks, I did it.

If someone needs how I did it, here is the complete code.

  • From Livewire Component Side
class CheckAddRemove extends Component {
    public $elements = [''];
    public $iterable = ['name', 'element', 'things', 'other thing'];

    public function addRaterField() {
        $this->elements[] = '';
    }

    public function removeRaterField($index) {
        unset($this->elements[$index]);
    }

    public function render(){
        return view('livewire.check-add-remove');
    }
}
  • From Livewire blade side
<div>
    @foreach ($elements as $elementIndex => $element)
        <div
            x-data="{
                removeField() {
                    $wire.removeRaterField({{ $elementIndex }})
                }
            }"
            wire:key="{{ $elementIndex }}">
            <div class="form-group">
                <label for="rater_type-{{ $loop->index }}">Element #{{ $loop->index + 1 }}</label>
                <x-select-two-field name="elements[]" placeholder="Select Rater Type #{{ $loop->index + 1 }}" required :iterable="$iterable" wire:model="elements.{{ $elementIndex }}" /> {{-- Select2 blade-x component --}}
            </div>
    
            <div class="d-flex justify-content-end">
                <div class="btn-group" role="group">
                    @if ($loop->last)
                        <button type="button" class="btn btn-success btn-sm" @click="$wire.addRaterField()" wire:key="{{ $elementIndex . '-btn' }}">Add Rater</button>
                    @endif
                    @if (($loop->last && !$loop->first) || !$loop->last)
                        <button type="button" class="btn btn-danger btn-sm" @click="removeField()">Remove Rater</button>
                    @endif
                </div>
            </div>
        </div>
    @endforeach
</div>
  • Select-Two blade-x component side
class SelectTwoField extends Component {
    public $iterable;
    public $placeholder;
    public $value;
    public $label;

    public function __construct($iterable, $placeholder, $label = '', $value = '') {
        $this->iterable = $iterable;
        $this->placeholder = $placeholder;
        $this->value = $value;
        $this->label = $label;
    }

    public function render() {
        return view('components.select-two-field');
    }
}
  • Select-Two blade-x blade side
<div
    x-data
    x-init="function() {
        $($refs.select2).select2({
            placeholder: '{{ $placeholder }}'
        })

        $($refs.select2).on('change', function() {
            @this.set($(this).attr('wire:model'), $(this).val())
        })
    }"
    wire:ignore>
    <select
    x-ref="select2"
    class="form-control"
    style="width: 100%"
    {{ $attributes }}>
        <option></option>
        @foreach ($iterable as $iterableItem)
            <option value="{{ $value ? $iterableItem->{$value} : $iterableItem }}">
                {{ ucwords(implode(' ', explode('-', $label ? $iterableItem->{$label} : $iterableItem))) }}
            </option>
        @endforeach
    </select>
</div>
1 Like

Very cool. Was just kinda taking a stab in the dark. Good on ya!