laravellaravel-livewire

Laravel livewire choice.js does not update the dynamic options


Choice.js shows correct options every time when I refresh the page, but its not updating options if user does not refresh the page. my component code is correct it passes correct data to blade template but choice.js shows first time loaded options.

Below is my component code

 public function render() {
    $this->setTaggedRoleOptions();
     return view('livewire.plans-table');

}

public function setTaggedRoleOptions() {
        $user = $this->user;

        $taggedPeople = SuccessionPlan::where('user_id', $this->user->id)
            ->orWhere(function ($query) use ($user) {
                $query->whereRaw("FIND_IN_SET(?, shared_with)", [$user->id]);
            });

        $taggedPeople = $taggedPeople->pluck('tagged_individual')->filter();


        // Use the plucked and filtered array in the query if it's not empty
        if ($taggedPeople->isNotEmpty()) {
            $this->troles = InternalPeople::where('company_id', $this->user->company_id)
                ->whereNotIn('id', $taggedPeople)
                ->limit(3)
                ->get()->map(function ($person) {
                    return [
                        'value' => $person->id,
                        'label' => $person->forename . ' ' . $person->surname . ' (' . (!empty($person->latest_role) ? $person->latest_role : "Not Provided") . ')',
                    ];
                })->toArray();
        } else {
            // Handle the case when $taggedPeople is empty or only contains null values
            // For example, you might want to return all records in this case
            $this->troles = InternalPeople::where('company_id', $this->user->company_id)
            ->limit(6)
            ->get()->map(function ($person) {
                return [
                    'value' => $person->id,
                    'label' => $person->forename . ' ' . $person->surname . ' (' . (!empty($person->latest_role) ? $person->latest_role : "Not Provided") . ')',
                ];
            })->toArray();
        }
        
    }

Below is my blade template code

<div class="mb-3">
    <label for="tagged-role" class="mb-1 block text-xs font-medium labelcolor">Tagged Role</label>
    <div  x-data="{
        value: @entangle('selectedTaggedRole'),
        options: {{ json_encode($troles) }},
        choicesInstance: null, // Variable to hold Choices instance
        init() {
            this.$nextTick(() => {
                this.choicesInstance = new Choices(this.$refs.selectTaggedRole, {
                    allowHTML: true,
                    placeholder: true,
                    placeholderValue: 'Select tagged role',
                    classNames: {
                        containerInner: 'p-2 border border-gray-300 rounded-md'
                    }
                });

                let refreshChoices = () => {
                                                                                                this.choicesInstance.clearStore();
                                                                        this.choicesInstance.setChoices(this.options.map(({ value, label }) => ({
                                value,
                                label,
                                selected: this.value === value,
                            })));
                            console.log('Choices Set:', this.choicesInstance.getValue(true));
                        };

                        refreshChoices();

                                                                this.$refs.selectTaggedRole.addEventListener('change', () => {
                this.value = this.choicesInstance.getValue(true);
                console.log('New Value:', this.value); // Debugging line
            });

            this.$watch('value', () => refreshChoices());
            this.$watch('options', () => refreshChoices());

            // Save Choices instance to window global variable
            window.myChoicesInstance = this.choicesInstance;
        });
    }
}" class="text-black w-full">
    <select x-ref="selectTaggedRole" class="my-select-class"></select>
</div>
@error('selectedTaggedRole')
<span class="text-red-500 text-xs">{{ $message }}</span>
@enderror
</div>

IF troles is already used then remove from the list.


Solution

  • You are not showing where/when the option list is updated

    Anyway, the basic problem is that, once set, the options property of the Alpine object, remains immutated and the method refreshChoices() works ever on the same dataset, also this.watch('options', ....) cannot trigger

    A simple workaround can be to entangle the options property to allow a dynamic content for it:

    options: @entangle('troles'),
    

    Some additional suggestions: