mrichar1 / jsonapi-vuex

Use a JSONAPI api with a Vuex store, with data restructuring/normalization.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Computed properties re-evaluation problem

pharkasbence opened this issue · comments

If I dispatch a jv/get action and fetch a particular data type, then it makes computed properties that return some other data types from the store re-evaluate. Is it normal, or do I something wrong?

If the collections are unrelated then I wouldn't expect to see this - however there are various things that can 'link' together collections, such as relationships, includes etc. Can you give some idea about how your API is organised, and if you have includes/relationships? Otherwise if they aren't linked, I might need to see how you are using your code to determine where the issue lies...

I have tested it with unrelated types too, and I experienced the same.

I sent 2 GET requests for the 2 unrelated data (with some timeout, to see clearly):

async created() {
    await this.$store.dispatch('jv/get', ['my-type', { params: {} }])
    setTimeout(() => {
         console.log('fetch my other type')
         this.$store.dispatch('jv/get', ['my-other-type', { params: {} }])
    }, 3000)
}

I have a computed prop:

myType() {
    console.log('my type computed evaluation')
    return this.$store.getters['jv/get']({ _jv: { type: 'my-type'} })
}

Now, the 'my type computed evaluation' log message appears twice: before and after 'fetch my other type' log message.

I compared the two values in the computed property and they are the same, so I don't understand what is happening.

(None of these types are in relationship with each other.)

I think the issue here might be a misunderstanding of what async/await will do on the vue lifecycle hooks.

Lifecycle events are fired off asynchronously. Setting async on created means that the code inside created will run synchronously, but the rest of the app startup continues without waiting for created to return. This is to prevent anything that blocks in created causing the app to block forever and never load.

So the order of events is actually something like:

  1. App starts loading, and calls the created hook.
  2. created hook blocks waiting for dispatch to return.
  3. App continues and renders the template, which includes a reference to the property myType.
  4. myType computed property runs, calls (synchronous) getter which logs and returns an object (empty).
  5. dispatch returns, allowing created to continue.
  6. setTimeout runs, then calls the getter which logs and returns an object (now full).

I'm a little confused as to why you are trying to do await in the created hook and then call the getter twice? This is exactly what computed properties are great for. Simply fire off the dispatch, and when it eventually returns, the computed property will be called (again), and the data variable updated.

Unfortunately this is not the problem. It's just a simplified example, I am not using it this way in my project. In this example I wrote async there just not to start the timeout before the previous request finished. If I remove async the result is the same.
The emtpy computed property run you mentioned is the very first, I didn't mention it because that is not important in this case. With that I have 3 evaluations (1 empty, and 2 not empty with the same value).

I'm not calling the getter, I'm just firing off the dispatch twice, the first is for get 'my-type' and the second is for get 'my-other-type'. The problem is that both of these are make myType computed property re-evaluate with the same (not empty) value.

Hi,

Thanks for flagging this up - it looks like the getter is returning a different object each time it is called - probably due to either the recursion and/or the deep copying that occurs when resolving the store.

I'll have a look and see if I Can figure out how to return 'identical' objects so that the computed property cache isn't being invalidated and causing it to fire again.

Hi, I have found a similar problem:

If I re-fetch some data (which is already in the store) with relationships, the computed property of the store item doesn't re-evaluate.

It is the inverse of the above problem, but there may be some connection between them :)

I've dug down into the code and created smaller test cases and it seems that this behaviour is not related to this module but is fundamental to how Vue works.

Since we are using method-style access on getters, and storing the data deeply nested, we have to use Vue.set to 'kick' the store when deeply nested data updates (Vue watches the store shallowly by default). This seems to cause the getters to re-run, and as they are returning functions which aren't cached they always return, even though their 'part' of the store is unchanged.

There are a few workarounds available:

  1. For simple cases, read the store directly in a computed property - e.g. return this.$store.state.my-type.id - that way you aren't relying on the getters which are firing unnecessarily.

  2. I haven't tried this yet, but I believe you could wrap the jsonapi-vuex getters with your own getters using property-style access. For example:

const getters = {
  getMyType: (state, getters) => {
    return getters['jv/get']('my-type')
  }

const store = new Vuex.Store({
  getters: getters,
  modules: ...
}

I believe that even if the jv/get getter fires more than once, if it returns the same data then the caching in the getMyType getter will not fire again, so in your computed property you can do:

computed: {
  myType() {
    return this.$store.getters['getMyType']
  }
}

I haven't test this though, so it's just a theory at the moment!

I'll have a look at the issue of re-fetching objects now with relationships triggering computed properties - if I find anything I'll move this into a separate issue.

I might be misunderstanding how this package works under the hood, I'm just wondering, if we doesn't set a particular key in the store, then why it kicks the computed prop. of that key.
So, should setting _jv.type1 with Vue.set() touch _jv.type2? I tried to test it, and it seems not. If I have a method-style getter for _jv.type2 it doesn't run. I might be doing something wrong.

As I understand it (and this is relatively 'low-level' in Vue so a lot of it is beyond my current understanding!) vuex only watches the 'first level' in the store - that is the top-level keys in the object/array. If you modify deeper keys, then it won't notice.

This is where Vue.set comes in - as I understand it, doing:

Vue.set(roott[a], 'b', True)

Causes all watchers of root to be notified, hence the false-positives.

I'm still digging into this a bit, but I need to find time to really grok the Vuex code and understand what is happening 'underneath the hood'.

commented

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.