2

I'm trying to filter a list when typing to text box which I get from Ajax call. The problem seems to be that the filter is applied before Ajax is ready.

HTML:

<input type="text" class="form-control" v-model="searchTerm">
<table>
    <tr v-for="food in filteredItems">
      <td>{{ food.name }}</td>
      <td>{{ food.energy }}</td>
    </tr>
</table>

helpers/index.js:

export default {
  getFoods() {
  return Vue.http.get('http://localhost:3000/foods/allfoods')
         .then((response) => {
             return response.data;
    });
  }
}

Vue component:

import helpers from '../helpers'
export default {
  name: 'Search',
  mounted() {
    helpers.getFoods().then((response) => {
      this.foodData = response;
    });
  },
  data() {
    return {
      searchTerm: '',
      foodData: [],
    }
  },
  computed: {
    filteredItems() {
      return this.foodData.filter(function(food){return food.name.toLowerCase().indexOf(this.searchTerm.toLowerCase())>=0;});
    }
  }

When I load the page or start typing I get

'TypeError: undefined is not an object (evaluating 'this.searchTerm')'.

Everything works perfectly if I hard-code the foodData array.

Have I misunderstood something and/or what am I doing wrong?

Ali Turki
  • 1,265
  • 2
  • 16
  • 21

1 Answers1

0

In your callback for the filter function in computed, this is not pointing to the Vue.

computed: {
  filteredItems() {
    return this.foodData.filter(function(food){
      // in this next line, this.searchTerm is likely undefined
      // because "this" is not the Vue
      return food.name.toLowerCase().indexOf(this.searchTerm.toLowerCase()) >= 0;
    });
  }
}

This is because you're using function(food){...} as your callback, and this will be the containing scope. Instead, use an arrow function, a closure, or bind.

computed: {
  filteredItems() {
    return this.foodData.filter(food => {
      // in this next line, this.searchTerm is likely undefined
      // because "this" is not the Vue
      return food.name.toLowerCase().indexOf(this.searchTerm.toLowerCase()) >= 0;
    });
  }
}

console.clear()

const foods = [{
    name: "orange",
    energy: 10
  },
  {
    name: "apple",
    energy: 8
  },
  {
    name: "banana",
    energy: 12
  },
  {
    energy: 1000
  }
]

const Search = {
    name: 'Search',
    template: `
      <div>
        <input type="text" class="form-control" v-model="searchTerm">
        <table>
          <tr v-for="food in filteredItems">
            <td>{{ food.name }}</td>
            <td>{{ food.energy }}</td>
           </tr>
         </table>   
      </div>
    `,
    mounted() {
      setTimeout(() => this.foodData = foods, 500)
    },
    data() {
      return {
        searchTerm: '',
        foodData: [],
      }
    },
    computed: {
      filteredItems() {
        const searchTerm = this.searchTerm.toLowerCase()
        return this.foodData
           .filter(food => food.name && food.name.toLowerCase().includes(searchTerm))
      }
    }
  }
new Vue({
  el:"#app",
  components: {Search}
})
<script src="https://unpkg.com/vue@2.2.6/dist/vue.js"></script>
<div id="app">
  <search></search>
</div>

See How to access the correct this inside a callback.

Bert
  • 80,741
  • 17
  • 199
  • 164
  • Thank you for your answer. But unfortunately that did not solve it. By switching to arrow function I get 'TypeError: undefined is not an object (evaluating 'food.name.toLowerCase')' Thanks for the link. That is definitely worth reading. – Arto Saarinen Jul 20 '17 at 15:47
  • @ArtoSaarinen Do all your `food` objects have a `name`? – Bert Jul 20 '17 at 15:49
  • @ArtoSaarinen Added a working example (with a simulated API call). See if you can see where the difference is. – Bert Jul 20 '17 at 15:57
  • There actually might be one entry without name property. Database shows that there is 1345 entries and when doing find 'name' in atom it shows only 1344 occurrences. Got to look through all of them now. – Arto Saarinen Jul 20 '17 at 15:57
  • @ArtoSaarinen a missing `name` would definitely cause an issue in the code as written because you can't call `toLowerCase` on undefined. – Bert Jul 20 '17 at 15:59
  • @ArtoSaarinen Added a filter that will handle null names. – Bert Jul 20 '17 at 16:09
  • Thank you very much. That was it. One object is missing name. You saved my day! – Arto Saarinen Jul 20 '17 at 16:26