Error in render function: “TypeError: Cannot read

2019-08-20 23:26发布

问题:

I have been dealing with an issue using Vue, Vuex and Vue-Router. I'm building a flash cards app, fetching all the cards on main app creation, then using a Vuex getter to get each card by its id which is passed as a route parameter.

Relevant bits:

App.vue

export default {
  components: {
    'app-header': header,
  },
  data() {
    return {
    }
  },
  created() {
    this.$store.dispatch('getAllCards');
  }
}

The dispatch('getAllCards') is just pulling all the cards from the DB and committing to Vuex store.js.

Now I set up a getter:

getters: {
  cardById: (state) => (id) => {
    return state.allCards.find((card) => card._id === id);
  }
}

Here is Card.vue:

<template>
  <div>
    <br>
    <div v-if="flipped" class="container">
      <div class="box">
        <pre v-if="card.code"><code class="preserve-ws">{{card.back}}</code></pre>
        <p class="preserve-ws center-vertical" v-else>{{card.back}}</p>
      </div>
    </div>
    <div v-else class="container">
      <div class="box">
        <h1 class="title has-text-centered center-vertical is-2">{{card.front}}</h1>
      </div>
    </div>
</template>

<script>

export default {
  data() {
    return {
      card: {},
      flipped: false,
      general_card: false,
      code_card: true,
      random_card: false
    }
  },
  computed: {
  },
  methods: {
  },
  created() {
    this.card = this.$store.getters.cardById(this.$route.params.id);
  }
}
</script>

I am getting the TypeError referenced in the title. My understanding is that the created() hook happens after data() has been set up, so then I can assign {card} using the getter. Unfortunately this displays nothing...

If I assign card() as a computed property:

computed: {  
  card() {
    return this.$store.getters.cardById(this.$route.params.id);
  }
}

The card shows, but I still get that error in console. Any idea why? I looked at this and attempted that solution, but to no avail.

回答1:

The question don't have all the premise to get a correct answer. (We don't know the mutations, the state, neither the actions and we have no clues about app-header component) So we have to admit some hypothesis :

  1. state.allCards is an empty Array when you mount the component
  2. action getAllCards is an async function to retrieve or set state.allCards with a mutation

cardById's getters, return a function, so vuex can't apply reactive on a function. So if the state change, the getter won't be trigger correctly. To correct this, use computed as you mention here.

But it don't fix the undefined error, that from my point of view, is because the getters return undefined on mount.

getters: {
  cardById: (state) => (id) => {
    var card = state.allCards.find((card) => card._id === id);
    if(card) {
      return card;
    }
    // on undefined return a default value
    return {
      front:'default value'
    };
  }
}

You can see the implementation on this jsfiddle with no error on console.

Or you can have a loading state on undefined value for your card component.

If my hypothesis is wrong please provide a jsfiddle to help you.



回答2:

What you need is a derived state based on store state which is to return a filtered card based on a card id. This id is received to your component via the route params. So its better you use a computed property instead of passing arguments to the store getters

Instead of initializing card in data property make card a computed property like this:

computed:{
    card(){
        return this.$store.state.allCards.find((card) => card._id === this.$route.params.id);
    }
}

Note this

If a component needs derived store state based on its own state(in your case rourte params), it should define a local computed property



回答3:

I tried everyone else's solutions and they did not work. But I got it to work finally. Here is what worked for me:

I ended up including a top-level:

<div v-if="!card"> Loading card... </div>
<div v-else> Rest of card template </div>

That seems to have silenced the error. Also, the card lives as a computed property:

card() {
  return this.$store.getters.cardById(this.$route.params.id);
}


回答4:

I think it was throw error in this step

getters: {
  cardById: (state) => (id) => {
    return state.allCards.find((card) => card._id === id);
  }
}

in this step, it can not find _id of cars, because allcards was null;

and then you use computed instead, when allcards has been change, it will get again; you can change code like this

getters: {
  cardById: (state) => (id) => {
    return state.allCards.find((card) => card && card._id === id);
  }
}


回答5:

$route.params.id must be string, so what is the type of card._id? It seems each card._id is number, I think.