David Joy
David Joy

Reputation: 76

vue.js method return keeps updating

I am new to Vue js so sorry for the stupid question. I am using a v-data-table to loop through an array. I need the return from a method to not change, but when the method runs it changes for every row.

I have tried a computed value but for some reason you can't pass a variable into a computed field?

<template>
  <div>
    <v-data-table
      :items="responseData"
      class="elevation-1"
    >
      <template slot="items" slot-scope="props">
        <td :key="props.item.tmdbId" class="text-xs-right">
          This: {{checkMovieExists(props.item.tmdbId)}}
        </td>
      </template>
    </v-data-table>
  </div>
</template>

<script>
import axios from "axios";
export default {
  data() {
    return {
      allMovies: []
    };
  },
  mounted() {
    axios.get("XXXX")
      .then(response => (this.allMovies = response.data)) 
  },
  methods: {
    checkMovieExists(strMovieTmdbId){
      this.allMovies.forEach(movie => {
        if (movie.tmdbId == strMovieTmdbId) {
          return "Exists"
        }
      });                
    }
  }
}
</script>

Upvotes: 0

Views: 1212

Answers (1)

Sumurai8
Sumurai8

Reputation: 20737

Computed properties are calculated based on the internal state only. They are special in the sense that they only have to be recalculated whenever the internal state on which they depend is changed. They don't accept any arguments, because that would make them rely on more than reactivity of your state, which defeats this caching mechanism. You could use a computed property to process the data you got from the api and loop over that instead, but lets ignore that for now.

As pointed out in the comments, the problem with your method is that you are using a forEach loop with a function, and return in the inner function. The outer function does not return anything, and thus the return value is undefined. There are several ways you can do this, but I think using Array.prototype.some works best in your case. You pass this method a function which is executed for each item in the array you are invoking it on. The entire thing returns true if any of these invocations returns true, and otherwise it returns false.

movieExists(strMovieTmdbId){
  return this.allMovies.some(movie => movie.tmdbId === strMovieTmdbId);
}

You will notice that I return a boolean here. I do this because your function is called XYZExists. We can use this boolean response to output something appropriate in the template.

  <template slot="items" slot-scope="props">
    <td :key="props.item.tmdbId" class="text-xs-right">
      This: {{ checkMovieExists(props.item.tmdbId) ? 'Exists' : '' }}
    </td>
  </template>

As far as the use of scoped slots goes, I believe you are doing it mostly correct as long as this.allMovies is something like:

[
  {
    tmdbId: 1
  },
  {
    tmdbId: 2
  }
]

Keep in mind that the items slot renders an entire row, not just one cell. You will need to surround it with <tr> ... </tr>.


As thanksd commented below, using this method may cause performance issues if the template is rerendered a lot. After all, each time the method is invoked, it looks through an entire array, for every item in that array.

Instead we can prepare the data using a computed property. Since you only assign data once in the mounted hook, your computed property should only be calculated once. Of course, you should then avoid mutating this.allMovies to prevent recalculation of your computed property.

You can keep the method we created earlier, but instead of using this.allMovies to render the data, we are going to use this method to calculate the data... once. We use a map to create an extra property that contains the result of the method.

computed: {
  preparedData () {
    return this.responseData.map(
      row => {
        return {
          ...row,
          exists: this.movieExists(row.tmdbId)
        }
      }
    );
  }
}

Now, instead of using responseData (whatever that is), we use preparedData to render the table. Since we now have a precalculated property exists on each row, we can just check props.item.exists instead of having to invoke a function. When the template is rerendered, as long as responseData stays constant, we use the cached version of preparedData.

<v-data-table
  :items="preparedData"
  class="elevation-1"
>
  <template slot="items" slot-scope="props">
    <td :key="props.item.tmdbId" class="text-xs-right">
      This: {{ props.item.exists ? 'Exists' : '' }}
    </td>
  </template>
</v-data-table>

Upvotes: 2

Related Questions