0

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" :class="{hidden:new_buyer==false}">
                        <div id="new_buyer">
                            <font-awesome-icon icon="fa-solid fa-dollar-sign" />
                            <div class="alerts" :class="{hidden:alerts_buy==''}">{{ alerts_buy }}</div>
                            <div class="item_desc"><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.

4
  • A few hints here, the initial value of licences is an empty array, y then it receives a deep nested object, there a JS reactivity issue about this, as you mentioned Vue.set() would fix it, but I have not fully moved to Vue3 yet, still you could try Object.assign(). If there is an actual reactivity problem it should update the data after one request, if the data is not updated the the problem must be somewhere else. Commented Dec 5, 2022 at 19:11
  • I figured out the answer going to post it shortly, thanks for the hints! Commented Dec 5, 2022 at 19:29
  • @deadPoet My answer isn't perfect but appears to be working at least for the important change when the ajax comes in with the new values. Still no idea how to make it work with react dev tools changing it there. Which makes sense it might not work in there because of the setLicenses probably not being called Commented Dec 5, 2022 at 19:36
  • Your computed is changed only on component mount event. Maybe the licenses get changed after the mount event. Then, your handleUpdates method does not get called. As your computed does only depend on your component data, it is not updated. Commented Dec 5, 2022 at 19:45

2 Answers 2

1

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.)

Sign up to request clarification or add additional context in comments.

2 Comments

Yeah, watch is def the answer, although this doesn't work with the react dev tools sadly. Refer to my answer to see what I came up with? did I do anything that could be done better? I heard there is something called watchEffect but not sure how to use it or if that would be superior. It does work when the AJAX call calls the state setLicense methods at least though. So we'll call it a 90% win?
As it looks like your data reflect a lot your state, I would probably choose to move all of the component logic to the vuex state in that case (and drop the usage of mount event)
0

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']),
}

2 Comments

If anyone knows how to make it reactive even with vue dev tools would appreciate it!
Since you mentioned there is no reactivity on updating dev tools data, please tell me what happens when you update a second time the same data?, is the previous data you updated rendered or nothing at all?. I have found this behavior a few times with vue2 when you need update the data twice to get the rendering right, and most of the times it is because Vue doesn't know the structure of the objects inside the array, and as mentioned in my other comment, you shoud use something like Object.assign or even better Vue.set(), but since is not an option in Vue3 then there should be a way to fix it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.