3

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?

1

5 Answers 5

4

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/[email protected]/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/[email protected]/dist/vue.global.js"></script>
<div id='app'>
  <input :value="inputAmount" @input="handleAmountInput($event)" placeholder="Enter amount..." type="text">
  <pre>{{ inputAmount }}</pre>
</div>

Sign up to request clarification or add additional context in comments.

2 Comments

Oh wow, you're completely right. I totally missed the fact that inputAmount doesn't get mutated at all if the user enters letters. Setting it to null at the beginning of the function does the job perfectly, thank you. Edit: now that I think about it a bit more, I'm still confused as to why inputAmount shows up correctly when called elsewhere in the template, e.g. <p>{{inputAmount}}</p>, but the trick still works so I'll just go with it.
BTW you can use to limit input to just numbers: <input v-model.number="age" type="number" />
0

Have you tried using @change event

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

Comments

0

Use computed getter setter instead, Link :

example :

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

    } 
  } 

Comments

0

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, '');

Comments

0

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/[email protected]/dist/vue.global.js"></script>
<div id='app'>
  <input v-model="inputAmount" @input="handleAmountInput($event)" placeholder="Enter amount..." type="text">
  <pre>{{ inputAmount }}</pre>
</div>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.