LiteralMetaphore
LiteralMetaphore

Reputation: 661

Vue.js input value not reflecting value in component data

I have the following input:

<input :value="inputAmount" @input="handleAmountInput($event)" placeholder="Enter amount..." type="text">

I don't want 2-way binding with inputAmount because I want to clean the input of non-numeric characters in the handleAmountInput() function whenever the user inputs something:

handleAmountInput(e) {
    const cleanInput = e.target.value.replace(/\D/g, '');
    this.inputAmount = cleanInput;
    console.log(cleanInput);
},

The issue is, the input itself doesn't reflect this cleaned up string set to inputAmount. If I show inputAmount in a separate element or console.log it like in the snippet above, it shows up just fine, but binding the value to the input with :value doesn't seem to work and shows the full inputted string, including non-numeric characters. What am I doing wrong here and how do I get the input to show the cleaned up string?

Upvotes: 3

Views: 2430

Answers (5)

Maarten Breddels
Maarten Breddels

Reputation: 1484

While the above solution by Michal Levý works, it results in invalid values when you observe the inputAmount value. For instance when you type 12a, the JS console will print:

  • inputAmount was 1
  • inputAmount was 12
  • inputAmount was 12a
  • inputAmount was 12

I think a better pattern is to use value, and force the component to rerender using this.$forceUpdate(). In this case, you will see:

  • inputAmount was 1
  • inputAmount was 12

Note that SolidJS can also enter this inconsistent state. ReactJS, however, will restore the DOM element to the state of the VDOM and will not become inconsistent.

A better solution I think for VueJS is to always update the DOM element that triggered an event when it did not trigger a re-render so we do

const app = Vue.createApp({
  data() {
    return {
      inputAmount: '12'
    }
  },
  watch: {
    inputAmount(value) {
      console.log('inputAmount was', value);
      // using v-model, this watch would trigger with invalid values
    }
  },
  methods: {
    handleAmountInput(e) {
      this.inputAmount = e.target.value.replace(/\D/g, '');
      console.log(this.inputAmount);
      // if this.inputAmount does not change, vuejs does not rerender
      // so, force it
      this.$forceUpdate()
    },
  },
})

app.mount('#app')
<script src="https://unpkg.com/vue@3.1.5/dist/vue.global.js"></script>
<div id='app'>
  <input v-model="inputAmount" @input="handleAmountInput($event)" placeholder="Enter amount..." type="text">
  <pre>{{ inputAmount }}</pre>
</div>

Upvotes: 0

jeneen
jeneen

Reputation: 1

use v-model="inputAmount"? please see: https://cn.vuejs.org/v2/guide/forms.html
then you can just edit like this.inputAmount= this.inputAmount.replace(/\D/g, '');

Upvotes: 0

Use computed getter setter instead, Link :

example :

computed: {
    inputAmount: {
        get(){
            //perform your logic
            return 'value'            
        },
        set(newValue){
            this.value= newValue;
        }

    } 
  } 

Upvotes: 0

Michal Lev&#253;
Michal Lev&#253;

Reputation: 37953

I'm not yet sure why exactly your code doesn't work as I would expect it to, but the way to fix it is to use both v-model and @input handler at the same time...

const app = Vue.createApp({
  data() {
    return {
      inputAmount: ''
    }
  },
  methods: {
    handleAmountInput(e) {
      this.inputAmount = e.target.value.replace(/\D/g, '');
      console.log(this.inputAmount);
    },
  },
})

app.mount('#app')
<script src="https://unpkg.com/vue@3.1.5/dist/vue.global.js"></script>
<div id='app'>
  <input v-model="inputAmount" @input="handleAmountInput($event)" placeholder="Enter amount..." type="text">
  <pre>{{ inputAmount }}</pre>
</div>

Update

Ok, I now understand the reason why your code does not work. What happens:

  1. Value of inputAmount is for example '123' (reflected in <input>)
  2. User types a
  3. Your @input handler is called. It receives the value '123a', do it's job creating cleaned value '123' and assigns it into inputAmount
  4. From Vue POV the value of inputAmount did not changed at all so no re-render is required and <input> still shows '123a' even tho inputAmount has a value of '123'

So another way of fixing your code is just to assign some value <input> can never produce into inputAmount 1st just to trigger the update (demo below)

const app = Vue.createApp({
  data() {
    return {
      inputAmount: ''
    }
  },
  methods: {
    handleAmountInput(e) {
      this.inputAmount = null
      this.inputAmount = e.target.value.replace(/\D/g, '');
      console.log(this.inputAmount);
    },
  },
})

app.mount('#app')
<script src="https://unpkg.com/vue@3.1.5/dist/vue.global.js"></script>
<div id='app'>
  <input :value="inputAmount" @input="handleAmountInput($event)" placeholder="Enter amount..." type="text">
  <pre>{{ inputAmount }}</pre>
</div>

Upvotes: 4

Apurv Bhavsar
Apurv Bhavsar

Reputation: 620

Have you tried using @change event

<input :value="message" @change="getInput($event)" placeholder="edit me" />

Upvotes: 0

Related Questions