javascriptvue.jsbindingvuejs2disabled-input

vue js disable input if value hasn't changed


I have an input with an initial value:

<input type="text" v-model="name" ref="input" />
<button type="submit" :disabled="$refs.input.defaultValue == $refs.input.value">Submit</button>

However the disabled binding gives an error: Cannot read property defaultValue of undefined. Best way to do this without spamming vm.data too much?


Solution

  • The error:

    Cannot read property defaultValue of undefined

    Is because the ref is not available so soon:

    An important note about the ref registration timing: because the refs themselves are created as a result of the render function, you cannot access them on the initial render - they don’t exist yet! $refs is also non-reactive, therefore you should not attempt to use it in templates for data-binding.

    And when you add it to the button's template, it tries to use it too soon.

    The workaround would be to add a simple conditional:

    <button type="submit" :disabled="!$refs.input || $refs.input.defaultValue == $refs.input.value">Submit</button>
    

    But don't be happy just yet.


    The defaultValue won't have the value you think

    When using v-model, defaultValue will actually always be empty string ("") because Vue initially renders the <input> with an empty value.

    To use a variable in the disabled button like you want, my suggestion is: use a mouted() logic to "save" the initial value and, in the button template, compare to it instead of defaultValue.

    Demo below.

    new Vue({
      el: '#app',
      data: {
        name: 'Hello Vue.js!'
      },
      mounted() {
        this.$refs.input.dataset.defVal = this.$refs.input.value;
      }
    })
    <script src="https://unpkg.com/vue"></script>
    
    <div id="app">
      <p>{{ name }}</p>
      <input type="text" v-model="name" ref="input" />
      <button type="submit" :disabled="!$refs.input || $refs.input.dataset.defVal == $refs.input.value">Submit</button>
    </div>


    Alternative: Going Vue all the way

    Of course, if it's a possibilty, you should take advantage of Vue's data-driven reactive nature, as tackling with the DOM is always tricky.

    The solution would be to just create another variable and populate it on mounted():

    new Vue({
      el: '#app',
      data: {
        name: 'Hello Vue.js!',
        defaultName: null
      },
      mounted() {
        this.defaultName = this.name;
      }
    })
    <script src="https://unpkg.com/vue"></script>
    
    <div id="app">
      <p>{{ name }}</p>
      <input type="text" v-model="name"/>
      <button type="submit" :disabled="name == defaultName">Submit</button>
    </div>

    Or, if you can set both name and defaultName to the same initial value, the mounted() logic above wouldn't even be necessary.