3

I'd like my view, using an id passed as a prop, to lookup an object's property in the store. The object in the store appears asynchronously, so the object might not be present right away. I'd like the view to react to the object's eventual appearance.

<template>
  <div>
    <h1>{{ title }}</h1>
  </div>
</template>

<script>
export default {
  props: ['id'],
  computed: {
    widget () {
      let path = `widget/${this.id}`
      return this.$store.state.widgets[path]
    },
    title () {
      let widget = this.widget
      return (widget) ? widget.data().fullName : 'nothing to see here'
    }
  }
}
</script>

Using vuex debug tools, I can watch the store widgets object start out empty and then get set with widgets: { 'widgets/someId': { ... } }, but my vue doesn't seem to pick up the change. The title remains == 'nothing...'.

I tried making these methods, but I get the same behavior. I also tried replacing the whole widgets object on the store, instead of one prop at a time (I think that's a requirement), but still no luck.

I think my question is very similar to this one, but the answer there is too terse. It just says "use a data item", but I don't really know what that is or how to use it (or why that will make the vue reactive).

goodson
  • 727
  • 2
  • 14
  • 24
  • Can you show the vuex store code for widgets? – ljubadr Feb 15 '19 at 19:11
  • Have you considers using [getters](https://vuex.vuejs.org/guide/getters.html#property-style-access)? They have the advantage of only being re-evaluated based on store changes. You can "chain" the getters using the 2nd argument that exposes other getters. You can evalute `title` based on respective computed `widget`. https://stackoverflow.com/a/53993174/5059657 – Alexander Staroselsky Feb 15 '19 at 19:13
  • Yes, computed properties aren't very reactive. I had the same problem some time ago. I ended up using `watchers`: https://vuejs.org/v2/guide/computed.html#Watchers – amedina Feb 15 '19 at 19:21
  • @AlexanderStaroselsky - I did try a getter with the same result. I think the reason is that my getter needs a parameter (the `path`), so my getter needs to return a function, and (maybe??) that stops it from being reactive? – goodson Feb 15 '19 at 19:46

2 Answers2

6

Mutations Follow Vue's Reactivity Rules

Since you're watching it go from empty to having some members, you are falling afoul of Vue's change detection caveats.

Vue cannot detect property addition or deletion.

You need to use Vue.set when adding or deleting members, even in Vuex.

tony19
  • 125,647
  • 18
  • 229
  • 307
Roy J
  • 42,522
  • 10
  • 78
  • 102
  • Thanks. That fixed it! Just to clarify, I have several bits of code in my store setters that say `state.someWidget.someProp = 'foobar'`. To become reactive, should I change all of those to `Vue.set(state.someWidget, 'someProp', 'foobar')` ?? – goodson Feb 15 '19 at 20:08
  • If the property you're setting already exists, it will be reactive. You only need `set` if you add or remove a property (after you create the containing object). – Roy J Feb 15 '19 at 20:24
0

Your widget is most likely not reactive, because widget itself does not change, and neither does widget.data because it is a function you define. Vue does not "see" the changes in data when data is called, because it is a function.

You have several ways of making your widget reactive. One is by precomputing your data and returning an object without functions. This could mean a performance drop when you have a lot of widgets, even though you do not need the data in most of them.

Another one is by potentially using the relatively new getter function/method. You would get your data with widget.data and have a widget like

{
  id: 1,
  get data() {
    // Whatever was in your function before
  }
}

Remember that this is (I believe) an Ecmascript 6 feature, so you will likely need to run this through babel to be compatible with all relevant browsers.

Sumurai8
  • 20,333
  • 11
  • 66
  • 100
  • Thanks. I'm stuck with that data() function because the objects are from google firestore. It turned out that using Vue.set() was enough to make my vue see the change (and call the data() function again). – goodson Feb 15 '19 at 20:11