Search code examples
modelfetchselectedalpine.js

Alpine JS not correctly update x-model


I have a problem with the selected option in simple Alpine form part. My selectbox get its values from an api. The x-model is set before the fetch. The selectbox sometimes shows the correct selected value and sometimes not. Even if I'll set the value after fetch then the selected option is not (always) selected. How I'll make sure that always the correct selected value is set?

Note: the value of selectbox 2 depends on the value who's set on the first. But if both set they must both show the correct selected value who's given at valueA and valueB.

This is my function

<script>
function alpineSelectFunction() {
    return {
        valueA: '100',
        arrayA: [],
        valueB: '101',
        arrayB: [],
        isLoading: false,
        apiUrl: "apiurl",
        fetchArrayA() {
            fetchUrl = this.apiUrl + `arrayA.json`;
            this.isLoading = true;
            fetch(fetchUrl)
                .then(res => res.json())
                .then(data =>  this.arrayA = data.data);
            this.isLoading = false;
        },
        fetchArrayB() {
            fetchUrl = this.apiUrl + `arrayB.json?id=${this.valueA}`;
            this.isLoading = true;
            fetch(fetchUrl)
            .then(res => res.json())
            .then(data => this.arrayB = data.data);
            this.isLoading = false;
        },
        init() {
            this.fetchArrayA();
            this.fetchArrayB();
        }
    }
}
</script>

This is the html

<div x-data="alpineSelectFunction()" x-init="init()">
    <select name="valueA" x-model="valueA" :disabled="isLoading" x-on:input.debounce.750="fetchArrayB()">
        <option value="">Choose...</option>
        <template x-for="option in arrayA">
            <option :value="option.id" x-text="option.title"></option>
        </template>
    </select>

    <select name="valueB" x-model="valueB" :disabled="isLoading">
        <option value="">Choose...</option>
        <template x-for="option in arrayB">
            <option :value="option.id" x-text="option.title"></option>
        </template>
    </select>
</div>

Solution

  • In this case, you've got an issue because x-model gets run before x-for.

    To circumvent this, you can bind to selected:

    <div x-data="alpineSelectFunction()" x-init="init()">
        <select name="valueA" x-model="valueA" :disabled="isLoading" x-on:input.debounce.750="fetchArrayB()">
            <option value="">Choose...</option>
            <template x-for="option in arrayA">
                <option :value="option.id" :selected="option.id === valueA" x-text="option.title"></option>
            </template>
        </select>
    
        <select name="valueB" x-model="valueB" :disabled="isLoading">
            <option value="">Choose...</option>
            <template x-for="option in arrayB">
                <option :value="option.id" :selected="option.id === valueB" x-text="option.title"></option>
            </template>
        </select>
    </div>
    

    Key code are the following lines:

    <option :value="option.id" :selected="option.id === valueA" x-text="option.title"></option>
    
    <option :value="option.id" :selected="option.id === valueB" x-text="option.title"></option>
    

    You can find a discussion/explanation to the issue (& another potential workaround) at https://github.com/alpinejs/alpine/issues/495#issuecomment-629671762.