Auto-select first <select> option when loading async options in Vue 3

Viewed 2418

I have a <select> HTML element which I want to v-model bind to a ref value in Vue.js 3's setup() method. So when a user selects a different option, the Form.ProductID ref updates.

Here is the <select> code:

<select v-model="Form.ProductID" id="ProductID" name="ProductID">
  <option value="1">Option A</option>
  <option value="2">Option B</option>
  <option value="3">Option C</option>
</select>

And setup():

export default {
  name: "ComponentProductSelector",
  setup() {
    const Form = ref({
      ProductID: '2',
      Price: null,
      Currency: null
    })

    onMounted(() => Form.value.ProductID)
    document.querySelector("#ProductID option:first-of-type")
  }
}

On first load in vue devtools, it shows the data as being:

Form (Object Ref) 
    ProductID: "[object HTMLOptionElement]"

When I select an option in the <select> element, Form.ProductID updates as expected and shows which option I selected e.g.:

 Form (Object Ref) 
        ProductID: 3

The problem is that on the page first load, the <select> element is not selecting the option with value="2" even though I am hard coding it in the setup(). It just shows a blank option! However if I change the <select> element to the following code then it does:

<select ref="Form.ProductID" id="ProductID" name="ProductID">
  <option value="1">Option A</option>
  <option value="2">Option B</option>
  <option value="3">Option C</option>
</select>

Now the option with value="2" is selected by default when the component is rendered, however the actual value of Form.ProductID does not update and vue devtools continues to show ProductID: "[object HTMLOptionElement]" as the data.

How can I get the <select> element to work using v-model and also select a default option when the component loads?

1 Answers

Answering the updated question in comments about how to select the first option when async loading. Once the data is loaded, set the value of Form to the first item in the options array (cloned to avoid mutating it), rather than manually manipulating the input DOM.

For example:

<select v-model="Form.ProductID" id="ProductID" name="ProductID" v-if="options">
  <option v-for="option in options" :key="option.ProductID" :value="option.ProductID">
    {{ option.ProductID }}
  </option>
</select>
setup() {
  const options = ref(null);
  const Form = ref(null);

  axios.get('...').then(response => {
    options.value = response.data;
    Form.value = { ...options.value[0] };    // Clone and select first option
  });
  return { options, Form }
}

There's a v-if on the <select> to delay its rendering until the data is ready.

Here's a demo:

const { createApp, ref } = Vue;
const app = createApp({
  setup() {
    const options = ref(null);
    const Form = ref(null);
    
    axios.get('https://jsonplaceholder.typicode.com/posts').then(response => {
      options.value = response.data;
      Form.value = { ...options.value[0] };    // Clone and select first option
    });
    return { options, Form }
  }
});
app.mount("#app");
<div id="app">
  <select v-model="Form.id" id="ProductID" name="ProductID" v-if="options">
    <option v-for="option in options" :key="option.id" :value="option.id">
      {{ option.id }}
    </option>
  </select>
</div>

<script src="https://unpkg.com/vue@next"></script>
<script src="https://unpkg.com/axios"></script>

Related