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

longestdrive's avatar

Livewire multi-select

Hi New to livewire and Alpinejs and working my way through some examples.

I'm trying to create a Livewire component that when options are selected they are displayed in a list.

I have this Livewire class:

<?php

namespace App\Livewire\Forms;

use Livewire\Component;

class MultiSelect extends Component
{
    public $options = [];

    public $selectedOptions = [];

    public function mount($options)
    {
        $this->options = $options;
    }

    public function selectOption($option)
    {
        if (! in_array($option, $this->selectedOptions)) {
            $this->selectedOptions[] = $option;
        }
    }

    public function removeOption($option)
    {
        $this->selectedOptions = array_filter($this->selectedOptions, function ($selected) use ($option) {
            return $selected !== $option;
        });
    }

    public function render()
    {
        return view('livewire.forms.multi-select');
    }
}

I have this view for the component:

<div>
    <div x-data="dropdown()" x-init="loadOptions()">
        <select id="players_select" x-on:change="selectOption($event.target.value)" class="form-select">
            <option value="">Select an option...</option>
            @foreach($options as $key => $value)
                <option value="{{ $key }}">{{ $value }}</option>
            @endforeach
        </select>

        <ul>
            <template x-for="(selected, index) in selectedOptions" :key="index">
                <li>
                    <span x-text="selected.text"></span>
                    <button type="button" x-on:click="removeOption(selected.value)">Remove</button>
                </li>
            </template>
        </ul>
    </div>
</div>

<script>
  function dropdown() {
    return {
      options: @entangle('options') || [],
      selectedOptions;
  : @entangle('selectedOptions') ||
    [],
      selectOption(option);
    {
      const selectedOption = this.options.find(opt => opt.value == option);
      if (selectedOption && !this.selectedOptions.includes(selectedOption)) {
        this.selectedOptions.push(selectedOption);
          @this.
        call('selectOption', option);
      }
    }
  ,
    removeOption(option);
    {
      this.selectedOptions = this.selectedOptions.filter(selected => selected.value != option);
        @this.
      call('removeOption', option);
    }
  ,
    loadOptions();
    {
      const selectElement = document.getElementById('players_select');
      if (selectElement) {
        const options = selectElement.options;
        for (let i = 0; i < options.length; i++) {
          this.options.push({
            value: options[i].value,
            text: options[i].innerText,
            selected: options[i].getAttribute('selected') != null ? options[i].getAttribute('selected') : false
          });
        }
      }
    }
  }
    ;
  }
</script>

and I use the component in a view like this:

<livewire:forms.multi-select :options="$playersSelect" />

$playersSelect is an array of key value pairs (ID and name)

When trying to use I get this error:

Alpine Expression Error: selectedOptions is not defined

Expression: "selectedOptions"

I think I have declared but unsure if it's in the right place and how to resolve.

0 likes
2 replies
LaryAI's avatar
Level 58

The error you're encountering, "Alpine Expression Error: selectedOptions is not defined," indicates that selectedOptions is not properly initialized in your Alpine.js component. Let's fix this by ensuring that selectedOptions is correctly defined and initialized.

Here's the corrected version of your Livewire component and view:

Livewire Component (MultiSelect.php)

<?php

namespace App\Livewire\Forms;

use Livewire\Component;

class MultiSelect extends Component
{
    public $options = [];
    public $selectedOptions = [];

    public function mount($options)
    {
        $this->options = $options;
    }

    public function selectOption($option)
    {
        if (!in_array($option, $this->selectedOptions)) {
            $this->selectedOptions[] = $option;
        }
    }

    public function removeOption($option)
    {
        $this->selectedOptions = array_filter($this->selectedOptions, function ($selected) use ($option) {
            return $selected !== $option;
        });
    }

    public function render()
    {
        return view('livewire.forms.multi-select');
    }
}

Livewire View (multi-select.blade.php)

<div>
    <div x-data="dropdown()" x-init="loadOptions()">
        <select id="players_select" x-on:change="selectOption($event.target.value)" class="form-select">
            <option value="">Select an option...</option>
            @foreach($options as $key => $value)
                <option value="{{ $key }}">{{ $value }}</option>
            @endforeach
        </select>

        <ul>
            <template x-for="(selected, index) in selectedOptions" :key="index">
                <li>
                    <span x-text="selected.text"></span>
                    <button type="button" x-on:click="removeOption(selected.value)">Remove</button>
                </li>
            </template>
        </ul>
    </div>
</div>

<script>
  function dropdown() {
    return {
      options: @entangle('options') || [],
      selectedOptions: @entangle('selectedOptions') || [],
      selectOption(option) {
        const selectedOption = this.options.find(opt => opt.value == option);
        if (selectedOption && !this.selectedOptions.includes(selectedOption)) {
          this.selectedOptions.push(selectedOption);
          @this.call('selectOption', option);
        }
      },
      removeOption(option) {
        this.selectedOptions = this.selectedOptions.filter(selected => selected.value != option);
        @this.call('removeOption', option);
      },
      loadOptions() {
        const selectElement = document.getElementById('players_select');
        if (selectElement) {
          const options = selectElement.options;
          for (let i = 0; i < options.length; i++) {
            this.options.push({
              value: options[i].value,
              text: options[i].innerText,
              selected: options[i].getAttribute('selected') != null ? options[i].getAttribute('selected') : false
            });
          }
        }
      }
    };
  }
</script>

Usage in a Blade View

<livewire:forms.multi-select :options="$playersSelect" />

Explanation

  1. Initialization of selectedOptions: Ensure selectedOptions is properly initialized in the dropdown function.
  2. Alpine.js Data Binding: Use @entangle to bind Livewire properties to Alpine.js.
  3. Event Handling: Ensure the selectOption and removeOption methods are correctly defined and called within the Alpine.js component.

This should resolve the "selectedOptions is not defined" error and allow your Livewire component to function as expected.

Please or to participate in this conversation.