VueJS - Digging into v-model

#javascript#vuejs

Vue.js has a v-model attribute which is pretty handy for quickly binding HTML input elements to JavaScript variables. Sometimes the need arises to customize this behavior - for example, re-emitting a change event to a parent component. I often end up doing this while building state-less components.

I’m writing this up as a reference for myself and others. This is a quick reference for how to replace v-model with props and events to allow for more customization.

Whether you’re using single-file components or the newer composition API, this is still applicable. To get the most out of this page, should already be familiar with the v-model section of the Vue.JS documentation: https://vuejs.org/guide/essentials/forms#form-input-bindings

In this document, I’m using $event for brevity, but you can (and probably should) use an arrow function as an event handler. For example, @input="x = $event.target.value" can be written as @input="event => x = event.target.value".

HTML Inputs

Bindings for traditional HTML input elements such as checkboxes, radio buttons, text inputs, etc.

Text Input / Text Area

This works for both text inputs and textarea elements.

<!-- v-model syntax -->
<input v-model="x" />

<!-- Equivalent using Props -->
<input :value="x" @input="x = $event.target.value" />

Checkbox

A checkbox is very similar to a text input, except we’re using the checked attribute instead of value to get the boolean state of the checkbox.

<!-- v-model syntax -->
<input type="checkbox" v-model="x" />

<!-- Equivalent using Props -->
<input type="checkbox" :checked="x" @change="x = $event.target.checked" />

Radio Buttons

Since radio buttons are typically grouped and when selected they emit a specific value, we need to determine checked depending on whether or not our variable equals the value for the radio button. After, when the radio button is selected, we need to update our variable to the value of the radio button.

Technically, you don’t need value, and can do the same thing as the above checkbox example, but if trying to mimic a traditional HTML input this is likely what you’ll need.

<!-- v-model syntax -->
<input type="radio" value="A" v-model="x" />
<input type="radio" value="B" v-model="x" />

<!-- Equivalent using Props -->
<input
  type="radio"
  value="A"
  :checked="x === 'A'"
  @change="x = $event.target.value"
/>
<input
  type="radio"
  value="B"
  :checked="x === 'B'"
  @change="x = $event.target.value"
/>

Select Dropdown

<!-- v-model syntax -->
<select v-model="x">
  <option value="a">A</option>
  <option value="b">B</option>
  <option value="c">C</option>
</select>

<!-- Equivalent using Props -->
<select :value="x" @change="x = $event.target.value">
  <option value="a">A</option>
  <option value="b">B</option>
  <option value="c">C</option>
</select>

Custom Components

In Vue.js 2.0, the prop used by v-model for custom compoents was called value and the event it emitted on changes was the @input event.

Now, in Vue 3, it’s called modelValue, and the event is @update:modelValue. Note <input> tags, etc. still use the value prop as that matches how you interact with the element using its vanilla JS property.

<!-- v-model syntax -->
<MyComponent v-model="x" />

<!-- in VueJS 2.0, this is what this is equivalent to -->
<!-- *WILL NOT WORK IN VUE 3.0* -->
<MyComponent :value="x" @input="x = $event" />

<!-- in VueJS 3.0, this is what this is equivalent to -->
<MyComponent :modelValue="x" @update:modelValue="x = $event" />

Binding Multiple Values

Vue 3.0 also supports multiple bindings. Any prop that confirms to the propName / @update:propName pattern can be used with v-model. This is useful for components that have multiple values to bind to. For example, if my prop name is a, I can use v-model:a="someVar" to bind to it.

<!-- v-model syntax -->
<SomeCustomComponent v-model:a="someVar" v-model:b="someOtherVar" />

<!-- Equivalent using Props -->
<SomeCustomComponent
  :a="someVar"
  :b="someOtherVar"
  @update:a="someVar = $event"
  @update:b="someOtherVar = $event"
/>

Modifiers

Vue also supports modifiers for v-model to handle different input types. Here’s how you can handle them using props:

Number Binding - v-model.number

This coerces the input value to a number. .number is automatically applied when an input’s type is number. This conversion is only done if the number is able to be parsed. So, using this on a textarea won’t force the value to be a number.

<!-- v-model syntax -->
<input type="number" v-model="x" />

<!-- Equivalent using Props -->
<input type="number" :value="x" @input="event => {
  const parsedValue = parseFloat(event.target.value)
  x = isNaN(value) ? event.target.value : parsedValue
}" />

Trimmed Strings - v-model.trim

This trims the input value before assigning it

<!-- v-model syntax -->
<input v-model.trim="x" />

<!-- Equivalent using Props -->
<input :value="x" @input="x = $event.target.value.trim()" />

Lazy Updates - v-model.lazy

This updates the value after the @change event, rather than on every @input event.

<!-- v-model syntax -->
<input v-model.lazy="x" />

<!-- Equivalent using Props -->
<input :value="x" @change="x = $event.target.value" />

Change Log

  • 5/1/2024 - Initial Revision

Found a typo or technical problem? file an issue!