I have a ecommerce app (SPA) built in Vue and below is the snapshot of the store
state: {
cart: [],
totalItems: {
count: 0
}
},
getters: {
totalItems(){
if(window.localStorage.totalItems){
return JSON.parse(window.localStorage.totalItems)
}
else{
let totalItems = { count: 0}
return totalItems
}
}
},
mutations: {
setCart(state, cart){
state.cart = cart
window.localStorage.cart = JSON.stringify(cart)
},
setTotalItems(state, totalItems){
state.totalItems = totalItems
window.localStorage.totalItems = JSON.stringify(totalItems)
}
},
actions: {
addToCart({ commit, getters }, productId){
let cart = getters.cart
let totalItems = getters.totalItems
if(cart.length == 0){
cart.push({id: productId, quantity: 1})
totalItems.count++
}
else if(cart.find(({ id }) => id == productId)){
let item = cart.find(({ id }) => id == productId)
item.quantity++
totalItems.count++
}
else{
cart.push({id: productId, quantity: 1})
totalItems.count++
}
commit('setCart', cart)
commit('setTotalItems', totalItems)
},
setTotalItems({ commit }, totalItems){
commit('setTotalItems', totalItems)
}
}
In my App.vue file is below -
<template>
<v-app>
<v-app-bar
app
color="red"
dark
>
<v-btn text to="/">Vue Shop</v-btn>
<v-spacer></v-spacer>
<v-btn text to="/cart">
<v-badge v-if="totalItems.count" :content="totalItems.count"><v-icon>mdi-cart</v-icon></v-badge>
</v-btn>
</v-app-bar>
<v-main>
<router-view></router-view>
</v-main>
</v-app>
</template>
<script>
export default {
name: 'App',
components: {
},
computed: {
totalItems(){
return this.$store.getters.totalItems
}
}
};
</script>
When I load the site I can see that the computed property calculates. However when I click on the 'Add to' button on the Home.vue file shown below it is supposed to
The issue I am facing is when I click on the 'Add to' button I can see that the value for totalItems in updated in localStorage but it is not reflecting in the v-app-bar component. It only does so if I navigate to a different page and then come back to the main page.
When I implemented by storing the value in the state instead of localStorage it reflects correctly, without having to navigate to a different page.
Is there a way to achieve this while still using localStorage instead of the store
<template>
<v-container>
<v-row>
<v-col v-for="product in products" :key="product.id">
<v-card width="300" height="300">
<v-img :src=product.imgUrl width="300" height="200"></v-img>
<v-card-title>
{{ product.name }}
</v-card-title>
<v-card-text>
${{ product.price }}
<v-btn small class="ml-16 primary" @click="addToCart(product.id)">Add to <v-icon>mdi-cart</v-icon></v-btn>
</v-card-text>
</v-card>
</v-col>
</v-row>
</v-container>
</template>
<script>
export default {
name: 'Home',
components: {
},
computed: {
products(){
return this.$store.getters.products
},
cart(){
return this.$store.getters.cart
}
},
methods: {
addToCart(id){
this.$store.dispatch('addToCart', id)
}
}
}
</script>
Problem Local Storage is not reactive, therefore your getter won't ever reevaluate.
Solution You could rewrite your getter so it only retrieves the value from the local storage if the state hasn't yet been initialized. Subsequent calls should then directly access the state, since you're changing that in your mutators and that will trigger a reevaluation of your getter and therefore your computed property:
getters: {
totalItems(state){
if(state.totalItems.count < 0){
return JSON.parse(window.localStorage.totalItems || '{count: 0}')
}
return state.totalItems;
}
},
You can then initialize your count
with -1
, so the first evaluation of the getter will look into the local storage.
By referencing the totalItems
property in your getter, vue knows to reevaluate this getter whenever this property changes.
The downside here is, that your getter and your state won't be in sync until the first change to the count.
You could probably also initialize your state with a call to localStorage, and eliminate it from the getter.
state: {
totalItems: {
count: JSON.parse(window.localStorage.totalItems || '{count: 0}')
}
},
getters: {
totalItems(state){
return state.totalItems;
}
},
Upside: your state and getter are in sync, but this also makes your getter redundant.