Home > database >  Trigger Reactivity in Vuex Store with array of objects
Trigger Reactivity in Vuex Store with array of objects

Time:12-06

I have the following store.js vuex file

import { createStore } from 'vuex'
import { Vue } from 'vue'

export default createStore({
    
    state: {
        sidebarHidden: false,
        route: {},
        loggedIn: false,
        loggedInEmail: '?',
        accountID: '??',
        loggedInFirstName: '???',
        // licenses: [],
        licenses:[
            {
                "id": 1,
                "name": "[email protected]",
                "key": "XXXXX-YYDLA-XXXXX-YDD4A",
                "product": "Some Product",
                "product_code": "1",
                "purchase_date": "2022-12-04T13:40:15.000Z",
                "stripe_productID": "prod_123",
                "active": false,
                "createdAt": "2022-12-04T13:40:15.000Z",
                "updatedAt": "2022-12-04T13:40:15.000Z",
                "user_id": 1,
                "subscriptions": [
                    {
                        "id": 16,
                        "stripe_subscriptionID": "fake_stripe_subscriptionID",
                        "stripe_productID": "prod_123",
                        "current_period_end": "2022-07-16T12:00:00.000Z",
                        "current_period_start": "2022-12-16T12:00:00.000Z",
                        "current_period_end_timestamp": 1670097600,
                        "current_period_start_timestamp": 1671220800,
                        "retry_count": 0,
                        "createdAt": "2022-12-05T00:36:56.000Z",
                        "updatedAt": "2022-12-05T00:36:56.000Z",
                        "user_id": 1,
                        "license_id": 1
                    }
                ]
            },
            {
                "id": 2,
                "name": "[email protected]",
                "key": "123456$",
                "product": "Autobot Assembler",
                "product_code": "2",
                "purchase_date": "2022-12-01T16:26:53.000Z",
                "stripe_productID": "garbage_product_id",
                "active": true,
                "createdAt": "2022-12-05T00:26:53.000Z",
                "updatedAt": "2022-12-05T00:26:53.000Z",
                "user_id": 1,
                "subscriptions": [
                    {
                        "id": 17,
                        "stripe_subscriptionID": "garbage_day",
                        "stripe_productID": "garbage_product_id",
                        "current_period_end": "2022-11-01T12:00:00.000Z",
                        "current_period_start": "2022-12-30T12:00:00.000Z",
                        "current_period_end_timestamp": 1667329200,
                        "current_period_start_timestamp": 1672430400,
                        "retry_count": 0,
                        "createdAt": "2022-12-05T00:42:57.000Z",
                        "updatedAt": "2022-12-05T00:42:57.000Z",
                        "user_id": 1,
                        "license_id": 2
                    }
                ]
            }
        ]
    },
    getters: {
        
    },
    mutations: {
        setSidebarHidden(state, value){
            console.log(`setSideBarhidden=${value}`);
            state.sidebarHidden = value;
        },
        SET_ROUTE(state, route) {
            console.log(`setting route to:${route}`);
            state.route = route;
        },
        setLoggedIn(state, value){
            console.log(`setLoggedIn=${value}`);
            state.loggedIn = value;
        },
        setLoggedInEmail(state, value){
            console.log(`setLoggedInEmail=${value}`);
            state.loggedInEmail = value;
        },
        setAccountID(state, value){
            console.log(`setAccountID=${value}`);
            state.accountID = value;
        },
        setLoggedInFirstName(state, value){
            console.log(`setLoggedInFirstName=${value}`);
            state.loggedInFirstName = value;
        },
        setLicenses(state, value){
            console.log(`setLicenses={value below}`);
            state.licenses = [];
            state.licenses.push(value);
            Vue.set(state,'licenses',value);
        }

    },
    actions: {
        getSession: async function(context) {
            return new Promise((resolve,reject) => {
                try{
                    (async() =>{
                        console.log("Getting session info if available...");
                        const url = `https://${document.location.hostname}/api/login/info`;

                        // const cookie = getCookie('sid');
                        // console.log('cookie below');
                        // console.log(cookie);

                        const options = {
                            method: "POST",
                            headers: {
                                'Content-Type': 'application/x-www-form-urlencoded',
                            },
                            credentials: 'include'
                        };
                        try{
                            var response = await fetch(url,options);
                            
                            const json_response = await response.json();
                            console.log("Showing json response below");
                            console.log(json_response);

                            if(response.ok){
                                console.log("Got login session info successfully!");
                                context.commit('setLoggedIn', true);
                                context.commit('setLoggedInEmail',json_response.email);
                                context.commit('setAccountID',json_response.account_id);
                                context.commit('setLoggedInFirstName',json_response.first_name);
                                context.commit('setLicenses',json_response.licenses);
                                resolve("Session Found");
                            }else{
                                console.log("Error getting session details.  Not logged in?");
                                context.commit('setLoggedIn', false);
                                reject('No Session Detected');
                            }
                        }catch(error){
                            // this.conf_err = true;
                            // this.conf_err_msg = "Too many attempts.  Please try again later.";
                            console.error(error);
                            reject(error);
                        }
                    })()
                }catch(error){
                    reject(error);
                }
            });
        }
    },
    modules: {}
})

My dashboard page has something that looks like this in the template section where I'm using it.

<a :href="buyurl" target="_blank" :>
                        <div id="new_buyer">
                            <font-awesome-icon icon="fa-solid fa-dollar-sign" />
                            <div  :>{{ alerts_buy }}</div>
                            <div ><div v-html="buy_inner_html"></div></div>
                        </div>
                    </a>

The javascript section of the file looks like this where the buy_inner_html computed property will use the vuex to describe how it should display:

<script>
    import { mapState } from 'vuex'
    import moment from 'moment';

    export default {
        methods: {
            handleUpdates: function(){
                //@JA - Read session state to determine if buy disabled is true.
                //@JA - It's determined to be true IF a license is found for the user.
                console.log("Printing Licenses from dashboard.vue...");
                console.log(this.licenses);
                var filteredLicenses = this.licenses.filter(function (el){
                    return el.product_code == '1'
                });
                try {
                    var filteredLicense = filteredLicenses[0];
                    this.license_info = filteredLicense;
                    console.log('Filtered License Below:');
                    console.log(filteredLicense);
                    if(filteredLicense.active == true){
                        let license_expiration_date = moment.unix(this.license_info.subscriptions[0].current_period_end_timestamp);
                        let current_date = moment();
                        this.new_buyer = false;
                        this.billing_disabled = false;
                        if(current_date.isAfter(license_expiration_date)){
                            this.license_state = 'expired';
                        }else{
                            this.license_state = 'active';
                        }
                        
                    }else{
                        this.new_buyer = false;
                        this.billing_disabled = false;
                        this.license_state = 'deactivated';
                    }
                } catch (error) {
                    this.new_buyer = true;
                    this.billing_disabled = true;
                    console.error(error);
                }
            }
        },
        computed: {
            ...mapState(['loggedInEmail','accountID','licenses']),
            billingurl: function(){
                return process.env.VUE_APP_BILLING_URL '?prefilled_email=' this.loggedInEmail
            },
            buyurl: function(){
                return process.env.VUE_APP_BUY_URL '?prefilled_email=' this.loggedInEmail '&client_reference_id=' this.accountID
            },
            buy_inner_html() {
                if(this.new_buyer==false){
                    if(this.license_state=="active"){
                        return `
                            <div>Thank you</div>
                            <div style="font-size:10px;padding-top:4px;">Expires: ${moment.unix(this.license_info.subscriptions[0].current_period_end_timestamp).format("MMMM Do, YYYY")}</div>`;
                    }
                    if(this.license_state=="expired"){
                        return `
                            <div>Expired</div>
                            <div style="font-size:10px;padding-top:4px;">${moment.unix(this.license_info.subscriptions[0].current_period_end_timestamp).format("MMMM Do, YYYY")}</div>`;
                    }
                    if(this.license_state=="deactivated"){
                        return `
                            <div>Inactive</div>
                            <div style="font-size:10px;padding-top:4px;">Please Renew Your Subscription</div>`
                    }
                    return `<div style="color:#F00">Error</div>`;
                }else{
                    return `<div>Buy Now!</div>`;
                }
            },
        },
        data: () => ({
            alerts_license_keys:'',
            alerts_security:'',
            alerts_account:'',
            alerts_products:'',
            alerts_videos:'',
            alerts_discord:'',
            alerts_youtube:'',
            alerts_billing:'',
            alerts_buy:'',
            billing_disabled:false,
            new_buyer:false,
            license_state:'expired',
            license_info:{subscriptions:[{"current_end_period_timestamp":"123456789"}]}
        }),
        mounted(){
            this.handleUpdates();
        }
    }
</script>
<style scoped>

The license state variable gets updated via an AJAX call during session grabbing and changes the vuex state variable licenses which the rest of the program can use.

The problem is when I update licenses I can't seem to get the page to react to the places where that vuex variable is being used in my buy_inner_html custom computed property function.

In particular if(this.license_state=="active")

If I try changing the value of this in the vuex dev tools it doesn't react/change like the other variables do right away?

enter image description here

I read that vue can't handle reactivity with arrays or objects. So with that in mind i've tried using vue.set which I read was supposed to resolve this but it still did't work.

What can I do to get vue to react to my vuex store licenses when I change it in the vue dev tools (or when I change it via an arbitrary ajax call or at some other time)?

(Note, that I know my code works because if I change the license active state manually in the vuex and then refresh the page I can get my page to display correctly on the active state. It only doesn't work if it changes after the refresh from an AJAX call or I try changing in vue dev tools)

EDIT: Apparently vue.set doesn't work in vue3 anymore, I didn't realize it wasn't working, but the problem still persists when testing with vue dev tools.

CodePudding user response:

The buy_inner_html computed only depends on component data that is (maybe) changed on component mount event.

You might be in a situation where the state is changed after this mount event.

Either watch for state changes and call the handleUpdates method, or make your computed directly dependent of the state (by moving your component data to your state i.e.)

CodePudding user response:

Vue3 no longer supports vue.set and it's not needed.

My setLicenses mutatator only needs to be this.

setLicenses(state, value){         
  state.licenses = value;
}

The real trick is that I needed a way to update the UI when this value changed.

In my template file the key to doing this was adding

watch: {
     deep: true,
     licenses: {
        handler(newValue, oldValue) {
           console.log("License vuex change detected");
           this.handleUpdates(newValue,oldValue);
        },
     }
},

This made it so it would pick up on the reactive changes to the variable. This is not perfect since changes in vuex dev tools still doesn't work, but at least it works whenever the setLicenses mutator is called!

Don't forget to add

<script>
    import { mapState } from 'vuex'

and

computed: {                   
    ...mapState(['loggedInEmail','accountID','licenses']),
}
  • Related