2

Based on the official example Custom Input Component example:

HTML:

<div id="app" >
  <div :class="{'has-value' : hasValue}">
    <input type="text"
      ref="input"
      v-bind:value="value"
      @input="onInput" >
  </div>
</div>

JS:

new Vue({
  el: '#app',
  data: function() {
    return {
      hasValue: false
      }
  },
  props: ['value'],
  methods:{
  onInput: function(){
    val= this.$refs.input.value
      if(val && val.length > 0){
        this.hasValue = true
      } else {
        this.hasValue = false
      }
    }
  }
})

JS Fiddle to play

Expected: Dynamically set hasValue data for input and bind a Css Class to this data

Unexpected: Only after initialization and typing the first Character, the typed character gets deleted from the input. Press JS-Fiddle->Run to see it again.

After first character everything works as expected.

If I remove v-bind:value="value" or comment \\this.hasValue = true it works as expected. Binding value prop is recommended for custom Input component.

Is this intended and why? Or is this a bug I should report?

3 Answers 3

12

This is expected behaviour:

  • When the first character is inserted, hasValue is changed from false to true, causing the component to re-render
  • During re-render, Vue sees that the input has a value (the character you just typed in), but the property you have bound to it (the value prop) is empty.
  • Therefore, Vue updates the input to match the bound prop - and thus, it empties the input.
  • After that, hasValue doesn't change anymore, so there's no re-rendering happening, and thus, Vue doesn't reset the input field's value anymore.

So how to fix this?

First you have to understand that Vue is data-driven - the data determines the HTML, not the other way around. So if you want your input to have a value, you have to reflect that value in the property bound to it.

This would be trivial if you worked with a local data property:

https://jsfiddle.net/Linusborg/jwok2jsx/2/

But since you used a prop, and I assume you want to continue using it, the sittuation is a bit different - Vue follows a pattern of "data down - events up". You can't change props from within a child, you have to tell the parent from which you got it that you want to change it with an event. The responsibility to change it would be the parent's. Here's your example with a proper parent-child relationship:

https://jsfiddle.net/Linusborg/jwok2jsx/4/

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

2 Comments

... causing the component to re-render was the key for me. Thanks for your in depth explanation.
Causing the component to re-render was the key for me as well. If it helps anyone the idea I had was to use something like ex: this.$el.querySelector('input').classList[hasValue]('has-value') where hasValue will be set before ex: const hasValue = Boolean(event.target.value) ? 'add' : 'remove' This would set the class directly and avoid render
2

I'm guessing that when you modified hasValue, Vue re-renders the component (to apply the has-value class to the div) which causes the v-bind:value to be re-evaluated. value is a prop which is unassigned and never changes, so the value of the input element gets cleared.

You really should use v-model on the input element and control has-value based on the value in the model, rather than interacting directly with the DOM. (You won't be able to use v-model with value though since value is a prop and cannot be assigned from within the component.)

I'm also guessing you want to make a custom input component which applies a certain style when it has a value. Try this:

Vue.component('my-input', {
  props: ['value'],
  template: `<input class="my-input" :class="{ 'has-value': value }" type="text" :value="value" @input="$emit('input', $event.target.value)">`,
});

new Vue({
  el: '#app',
  data: {
    value: '',
  },
});
.my-input.has-value {
  background-color: yellow;
}
<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>

<div id="app">
  <my-input v-model="value"></my-input>
</div>

Comments

0

Are you trying to achieve something like this?

new Vue({
  el: '#app',
  data: {
    input: ''
  },
  computed: {
    hasValue () {
      return this.input.length ? true : false
    }
  }
})
.has-value {
  background-color: red;
}
<div id="app">
  <div :class="{'has-value' : hasValue}">
    <input type="text" v-model="input">
  </div>
</div>

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

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.