Skip to content
Advertisement

Vuejs3/Vuex4 conditional render on promise fulfillment

I have custom objects for holding child objects full of data. The child objects are initiated with null values for all their properties, so the objects can be referenced and their properties filled from remote sources. This creates a lazy-loading setup.

This code is going to be extremely trimmed down, but everything relevant should be here:

class Collection extends Object {
    constructor(){
        this.loaded = false;
        var allLoaders = [];
        var loaderPropmises = [];
        var resolver;
        const $this = this;
        var trackLoaders = function(){
            $this.loaded = false;
            loaderPromises.push(Promise.all(allLoaders).then(() => {
                //... irrelevant logic in here to ensure only the latest promise sets loaded to true
                $this.loaded = true; //This is getting called where I expect
                resolver(); 
            }));
        }
        
        //hook for outside things to watch the promise if they want
        this.loader = new Promise((resolve) => {
            //this only gets resolved once, which is fine
            resolver = resolve;
        });


        //... bunch of code around adding child objects, but the important part:
        this.add(child){
            this[child.id] = child;
            this.allLoaders.push(child.loader); 
            trackLoaders();
        }
    }
}

The child then looks like:

class Child extends Object {
     constructor(){
         this.loaded = false;
         var resolver;
         const $this = this;
         this.loader = new Promise((resolve) => {
             resolver = resolve;
         }).then((){
             $this.loaded = true;
         });

         this.populate(data){
             //bunch of stuff to set data to properties on this object
             resolver();
         }
     }
}

In Vuex 4 I have these Collections as properties on an “AppData” object in the store:

const store = createStore({
    state: function(){
        AppData: {}
    },
    mutations: {
        setupCollection(state, name){
            if (!Object.hasOwnProperty.call(state.AppData, name){
                state.AppData[name] = new Collection();
            }
        }
    },
    actions: {
        //this is called on each row of data returned from an Axios call
        add (context, {name, data}){
            context.state.AppData[name][data.id].populate(data);
        }
    }
});

The idea is that whenever a Child is added to a Collection, the collection loaded property will be false until all the Child loader promises resolve. This all executes perfectly… Except that the loaded bools aren’t reactive.

Right now, I have a Promise.all in each component’s Created function that flags the component as “loaded” once all the objects needed for the component have had their “loader” promises resolved. This absolutely works, but isn’t ideal as different data will be available at different times, and there are sometimes hundreds or more of these classes on screen at once. What I’m trying to accomplish is:

<div v-if="!myCollection.loaded">
    Loading...
</div>
<div v-else>
    Show the data I want here {{myCollection.property}}
</div>

So I have two thoughts on overcoming this, either of which would be great:

  • VueJS3 no longer has a need for Vue.set(), because Proxies. How would I make the loaded bools here reactive then? Or more specifically, what am I doing that prevents this from working?

  • Alternatively, is there a practical way to use the loader promise directly in a template?

Answer

It looks like Vue’s ref is what I needed:

this.loaded = ref(false);

This works, at least on the Child class. I have some sort of circular referencing issue going on and haven’t been able to test on the Collection class yes, but it should work the same.

Advertisement