Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to prevent @change event when changing v-model value

Tags:

vuejs2

I'm building an auto-complete menu in Vue.js backed by Firebase (using vue-fire). The aim is to start typing a user's display name and having match records show up in the list of divs below.

The template looks like this:

<b-form-input id="toUser"
    type="text"
    v-model="selectedTo"
    @change="searcher">
</b-form-input>

<div v-on:click="selectToUser(user)" class="userSearchDropDownResult" v-for="user in searchResult" v-if="showSearcherDropdown">{{ user.name }}</div>

Upon clicking a potential match the intention is to set the value of the field and clear away the list of matches.

Here is the code portion of the component:

computed: {
  /* method borrowed from Reddit user imGnarly: https://www.reddit.com/r/vuejs/comments/63w65c/client_side_autocomplete_search_with_vuejs/ */
  searcher() {
    let self = this;
    let holder = [];
    let rx = new RegExp(this.selectedTo, 'i');
    this.users.forEach(function (val, key) {
      if (rx.test(val.name) || rx.test(val.email)) {
        let obj = {}
        obj = val;
        holder.push(obj);
      } else {
        self.searchResult = 'No matches found';
      }
    })
    this.searchResult = holder;
    return this.selectedTo;
  },

  showSearcherDropdown() {
    if(this.searchResult == null) return false;
    if(this.selectedTo === '') return false;
    return true;
  }
},

methods: {
  selectToUser: function( user ) {
    this.newMessage.to = user['.key'];
    this.selectedTo = user.name;
    this.searchResult = null;
  }
}

Typeahead works well, on each change to the input field the searcher() function is called and populates the searchResult with the correct values. The v-for works and a list of divs is shown.

Upon clicking a div, I call selectToUser( user ). This correctly reports details from the user object to the console.

However, on first click I get an exception in the console and the divs don't clear away (I expect them to disappear because I'm setting searchResults to null).

[Vue warn]: Error in event handler for "change": "TypeError: fns.apply is not a function"

found in

---> <BFormInput>
       <BFormGroup>
         <BTab>

TypeError: fns.apply is not a function
    at VueComponent.invoker (vue.esm.js?efeb:2004)
    at VueComponent.Vue.$emit (vue.esm.js?efeb:2515)
    at VueComponent.onChange (form-input.js?1465:138)
    at boundFn (vue.esm.js?efeb:190)
    at invoker (vue.esm.js?efeb:2004)
    at HTMLInputElement.fn._withTask.fn._withTask (vue.esm.js?efeb:1802)

If I click the div a second time then there's no error, the input value is set and the divs disappear.

So I suspect that writing a value to this.selectedTo (which is also the v-model object for the element is triggering a @change event. On the second click the value of doesn't actually change because it's already set, so no call to searcher() and no error.

I've noticed this also happens if the element loses focus.

Question: how to prevent an @change event when changing v-model value via a method?

(other info: according to package.json I'm on vue 2.5.2)

like image 965
Capn Sparrow Avatar asked Feb 26 '18 09:02

Capn Sparrow


1 Answers

On:

<b-form-input id="toUser"
    type="text"
    v-model="selectedTo"
    @change="searcher">

The "searcher" should be a method. A method that will be called whenever that b-component issues a change event.

But looking at your code, it is not a method, but a computed:

computed: {
  searcher() {
    ...
  },

  showSearcherDropdown() {
    ...
  }
},

methods: {
  selectToUser: function( user ) {
   ...
  }
}

So when the change event happens, it tries to call something that is not a method (or, in other words, it tries to call a method that doesn't exist). That's why you get the error.

Now, since what you actually want is to update searcher whenever this.selectedTo changes, to get that, it is actually not needed to have that @change handler. This is due to the code of computed: { searcher() { already depending on this.selectedTo. Whenever this.selectedTo changes, Vue will calculate searcher again.

Solution: simply remove @change="searcher" from b-form. Everything else will work.

like image 97
acdcjunior Avatar answered Sep 28 '22 14:09

acdcjunior