I have an increment field, I do everything right. Updates the states in the vue dev tols, etc... However it doesn't update the values of the component's buttons there. I've tried everything already. But the reactivity is not happening in this case.
I'll leave a part of the VUEX code to see if it helps:
export default {
namespaced: true,
state: {
productsInBag: []
},
mutations: {
ADD_TO_BAG(state, product) {
state.productsInBag.push(product);
localStorage.setItem("productsInBag", JSON.stringify(state.productsInBag))
},
REMOVE_FROM_BAG(state, productId) {
let updatedBag = state.productsInBag.filter(item => productId != item.id)
state.productsInBag = updatedBag
},
DECREASE_PRODUCT(state, product) {
let itemIndex = state.productsInBag.findIndex(x => x.id === product.id)
state.productsInBag[itemIndex].quantity--
},
INCREASE_PRODUCT(state, productId) {
state.productsInBag.find(item => item.id === productId).quantity++
}
},
actions: {
addToBag ({ commit }, payload) {
commit('ADD_TO_BAG', payload.product)
},
removeFromBag ({ commit }, payload) {
if (confirm('Você quer remover este produto do carrinho ?')) {
commit('REMOVE_FROM_BAG', payload.product.id)
}
},
decreaseProduct ({ commit }, payload) {
commit('DECREASE_PRODUCT', payload.product)
},
increaseProduct ({ commit }, payload) {
commit('INCREASE_PRODUCT', payload.product.id)
}
},
getters: {
getProductsInBag(state) {
return state.productsInBag
},
}
}
I'll also leave a part of the component code:
import globalMixin from '#/mixins/globalMixins'
import { mapState } from 'vuex'
import { mapGetters } from 'vuex'
export default {
name: 'CartView',
mixins: [globalMixin],
computed: {
...mapState('products', ['productsInBag'])
},
methods: {
...mapGetters("products", ["getProductsInBag"]),
}
}
<template>
<div class="cart container-fluid">
<div class="cart-item-products row" v-for="product in productsInBag" :key="product.id">
<div class="col-lg-3 cart-item-products-description">
<p>{{ product.name }}</p>
</div>
<div class="col-lg-3 cart-item-products-description">
<button #click="() => $store.dispatch({type: 'products/decreaseProduct', product})">-</button>
<span class="quantity">{{ product.quantity }}</span>
<button #click="() => $store.dispatch({type: 'products/increaseProduct', product})">+</button>
</div>
<div class="col-lg-3 cart-item-products-description">
<p>
<span class="value-description">{{ brazilianCurrency(product.price) }}</span> à vista ou {{ divideValue(product.price) }}
</p>
</div>
<div class="col-lg-3 cart-item-products-description">
<p>
<span class="value-description">{{ brazilianCurrency(product.price * product.quantity) }}</span> à vista ou {{ divideValue(product.price * product.quantity) }}
</p>
</div>
</div>
</div>
</template>
In Vue Devtools states work perfectly. Look:
Would be great If you can help me. I've been looking for a solution for this for 6 hours.
Vue. set is a tool that allows us to add a new property to an already reactive object and makes sure that this new property is ALSO reactive.
So, according to your problem, Vue.set will work well for you. Use it like this-
Vue.set(state.productsInBag, itemIndex, product)
Optional-
Also by looking at your fiddle's code, you can make a common function in your helper file to find the product's index like his-
export const findByIndex = (arr, matcher) => {
return arr.findIndex(item => item.id === matcher)
}
And import this function in your js file like this-
import { findByIndex } from "YOUR_HELPER_FILE_PATH"
Now, the mutations can use like this-
DECREASE_PRODUCT(state, product) {
let itemIndex = findByIndex(state.productsInBag, product.id)
if(itemIndex != -1) {
product.quantity--
Vue.set(state.productsInBag, itemIndex, product)
}
},
INCREASE_PRODUCT(state, product) {
let itemIndex = findByIndex(state.productsInBag, product.id)
if(itemIndex != -1) {
product.quantity++
Vue.set(state.productsInBag, itemIndex, product)
}
}
A Little Tip-
Instead of making two mutation methods, a single mutation can be created which will accept the product_id and operation (increase or decrease) in an object, like this-
UPDATE_PRODUCT(state, payload) {
// payload is an object which will have some data
let itemIndex = findByIndex(state.productsInBag, payload.product_id)
if(itemIndex != -1) {
payload.operation == 'increase' ? product.quantity-- : product.quantity++;
Vue.set(state.productsInBag, itemIndex, product)
}
}
Related
so in the console log it is saying it is being removed from the cart but i can still see the items in the cart... how can i remove them from the cart? im using pinia for state management and the cart is in the state. why it is not working for me?
the code:
shop.vue
<template>
<div class="shop">
Cart Items: <cart-badge :count="cartLength">{{ count }}</cart-badge>
<h1>shop</h1>
<div class="products" v-for="item in Products" :key="item.id">
{{ item.name }} {{ item.price }}$
<button #click="storeCounter.addToCart(item)">Add to Cart</button>
</div>
</div>
</template>
<script setup>
import { useCounterStore } from "../stores/counter";
import Products from "../db.json";
import CartBadge from "../components/CartBadge.vue";
import { computed } from "vue";
const storeCounter = useCounterStore();
const cartLength = computed(() => {
return storeCounter.cart.length;
});
</script>
store.js(pinia)
import { defineStore } from "pinia";
export const useCounterStore = defineStore("counter", {
state: () => ({
cart: [],
}),
actions: {
addToCart(id) {
this.cart.push(id);
console.log("test passed!");
},
removeFromCart(id) {
this.cart.splice(id);
console.log("removed from cart!");
},
},
});
cart.vue
<template>
<div class="cart">
<h1>cart</h1>
<div class="cartitems" v-for="item in storeCounter.cart" :key="item.id">{{ item.name }} {{ item.price }}$
<button #click="storeCounter.removeFromCart(item.id)">X</button>
</div>
</div>
</template>
<script setup>
import { useCounterStore } from '../stores/counter';
const storeCounter = useCounterStore()
</script>
Splice uses the index of the item to delete it.
Do this instead;
removeFromCart(id) {
let cartItemIndex = this.cart.findIndex(x => x.id === id);
if (cartItemIndex >= 0) {
this.cart.splice(cartItemIndex, 1);
console.log("Removed from cart");
}
}
or if you don't want to use splice anymore (and if performance matters);
removeFromCart(id) {
this.cart = this.cart.filter(x => x.id !== id);
console.log("Removed from cart");
}
Please use more descriptive parameter names. Rename "id" to "item" in addToCart, since you're not just adding the item id, but the entire item. This can be confusing to other developers and make your code unreadable.
addToCart(item) {
this.cart.push(item)
console.log('test passed!')
}
References: splice filter
I'm creating a social network for project in my formation, i have a like system and it work.
i have a components cardArticle with all info and i try to do a like count. It work but when i refresh the page or going on other page, i lost all my data likes (my data is not saved)
components/CardArticle.vue
<template>
<div id="card">
<div>
<a class="cardLink">
<img class="card-img" v-if="post.imageUrl !== undefined" :src="post.imageUrl" />
<h2 class="cardTitle"> {{ post.title }}</h2>
<p class="cardDescription"> {{ post.description }}</p>
</a>
</div>
<div class="buttonIcon">
<div>
<button type="button" class="buttonDelete" id="buttonDelete" #click="deletePost"
v-if="this.post.userId === this.user.userId || this.user.isAdmin === true">Supprimer</button>
<button type="button" class="buttonEdit" id="buttonEdit" #click="modifyPost"
v-if="this.post.userId === this.user.userId || this.user.isAdmin === true">
Editer
</button>
</div>
<div class="divIconLike">
<div class="iconLike">
<a #click="sendLike">
<i class="fa-regular fa-thumbs-up"></i>
</a>
</div>
<div class="countLike">
<p> {{ likes }} </p>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from "axios";
import router from "../router/index.js";
export default {
name: 'CardArticle',
data () {
return {
likes: 0
}
},
props: {
post: {
type: Object
}
},
computed: {
user() {
return this.$store.getters.user;
}
},
methods: {
sendLike() {
axios.post("http://localhost:3000/api/articles/" + this.post._id + "/like", {
userId: this.user.userId
}, {
headers: {
Authorization: "Bearer " + this.user.token
}
})
.then(response => this.likes = response.data.article.likes)
.catch(error => console.log(error))
}
}
}
</script>
views/home.vue
<template>
<div class="home" v-if="this.user.token !== null">
<CardArticle v-for="post in allPosts" v-bind:key="post.id" :post="post" />
</div>
</template>
<script>
import CardArticle from "../components/CardArticle.vue"
import axios from "axios";
export default {
name: 'HomeArticle',
data () {
return {
post: {
title: "",
description: "",
imageUrl: ""
},
allPosts: [],
}
},
computed: {
user() {
return this.$store.getters.user;
}
},
components: {
CardArticle,
},
mounted() {
axios.get("http://localhost:3000/api/articles", {
headers: {
Authorization: "Bearer " + this.user.token
}
})
.then(response => {
this.allPosts = response.data;
})
.catch(error => {
return error;
})
}
}
</script>
What i should do for not losing my data ?
I would not use vuex or localstorage for that if possible, you have idea ?
Thanks for your help
If you loading data from server, then refresh page, you always will be lose data, because browser loading page again from server, and application will load data again.
If you don't want use vuex (but why not?), you can write data to cookies (by setting cookie value), then load it on app startup (when page is loaded). But it's not best practice at all. You can use vue3-cookies lib (link).
By the way, better learn to use stores, most progressive, I think, is Pinia.
Check: https://pinia.vuejs.org/
i lost all my data likes (my data is not saved)
likes is belong to each articles and It should have been saved to your db and call API to retrieve it again on component mounting:
export default {
name: 'CardArticle',
data () {
return {
likes: 0 // It's not managed by component state
}
},
methods: {
sendLike() {
axios.post("http://localhost:3000/api/articles/" + this.post._id + "/like", {
userId: this.user.userId
}, {
headers: {
Authorization: "Bearer " + this.user.token
}
})
.then(
// invalidates, update allPosts props (emit to parent)
)
.catch(error => console.log(error))
}
}
}
I am sending data from vuex to localStorage, and then trying to set data from localStorage to state 'cart'.
import axios from 'axios'
import { createStore } from 'vuex'
export default createStore({
state: {
cart: [],
products: []
},
getters: {
cart: state => {
return state.cart
},
products: state => {
return state.products
}
},
mutations: {
addToCart(state, product) {
localStorage.carts = JSON.stringify(product)
return state.cart.push(product)
},
removeProduct(state, item) {
localStorage.removeItem('carts', item)
return state.cart.pop()
},
SET_PRODUCTS(state, products) {
state.products = products
state.filteredProducts = products
}
},
actions: {
addToCart(context, product) {
context.commit('addToCart', product)
},
removeProduct(context, item) {
context.commit('removeProduct', item)
},
fetchProducts({ commit }) {
return axios.get('http://localhost:3000/products?_limit=4&_page=1').then(res => {
commit('SET_PRODUCTS', res.data)
})
}
}
})
I have tried adding this line to the state cart
cart: [JSON.parse(localStorage.carts)]
but this only works if localStorage is not empty(if it already has something), if not it will throw error
Uncaught SyntaxError: Unexpected token u in JSON at position 0
Cart component:
<template>
<div class="cart-container">
<div class="header">
<p>NARUZDBA</p>
<div>
<img src="../assets/icons/close.png" class="icon" #click="this.$store.commit('customChange')" />
</div>
</div>
<div class="products" v-for="item in cart" :key="item.id">
<div class="product">
<div>
<p class="amount">1x</p>
<p>{{ item.name }}</p>
<icon name="remove" class="remove" #click="removeProduct(item)"></icon>
</div>
<p class="price">{{ item.price }}kn</p>
</div>
</div>
<router-link to="/cart">
<button class="btn">Kosarica</button>
</router-link>
<div class="outside" #click="away"></div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import Icon from './icons/Icon.vue'
export default {
components: { Icon },
methods: {
away() {
this.$store.commit('customChange')
},
removeProduct(item) {
this.$store.commit('removeProduct', item)
}
},
computed: {
...mapState(['cart'])
}
}
</script>
This is now showing '1x', remove button, and 'kn'
I have the gist of how to do this, but I'm a beginner in vue, and I'm struggling with how to put it together. I need Control.vue to update the index in Exhibitor.vue. I know I'll have an $emit event happening in Control when I click on the button to pass the index data to the parent, and I'd have to use props to pass data from Exhibitor to its children, but how? I can't understand how to pass the index of an array with my code.
Exhibitor.vue
<template>
<div id="exhibitor">
<section class="exhibitor_info">
<h1 class="exhibitor_name">{{ exhibitors[index].firstName }} {{ exhibitors[index].lastName }}</h1>
<h2>Tag Number: {{ exhibitors[index].tagNum }} <em>{{ exhibitors[index].species }}</em></h2>
</section>
<div class="frame"><img :src="getImgUrl(exhibitors[index].picture)" alt="Exhibitor-Picture" class="image"></div>
</div>
</template>
<script>
export default {
name: 'Exhibitor',
data() {
return {
exhibitors: [],
index: 0
}
},
created: function() {
this.fetchExhibitors();
},
methods: {
fetchExhibitors() {
let uri = 'http://localhost:8081/exhibitor'
this.axios.get(uri).then(response => {
this.exhibitors = response.data
})
},
getImgUrl: function(pic) {
return require('../assets/' + pic)
}
}
}
</script>
Display.vue
<template>
<div id="display">
<exhibitor></exhibitor>
<buyer></buyer>
</div>
</template>
<script>
import Exhibitor from './Exhibitor.vue';
import Buyer from './Buyer.vue';
export default {
components: {
'exhibitor': Exhibitor,
'buyer': Buyer
}
}
</script>
Control.vue
<template>
<div id="control">
<display></display>
<button v-on:click="incrementLeft">Left</button>
<button v-on:click="incrementRight">Right</button>
</div>
</template>
<script>
import Exhibitor from './Exhibitor.vue';
import Display from './Display.vue';
export default{
props: ['exhibitors', 'buyers', 'index'],
data() {
return {
index: 0
}
},
methods: {
incrementRight: function() {
// Note that '%' operator in JS is remainder and NOT modulo
this.index = ++this.index % this.exhibitors.length
},
incrementLeft: function() {
// Note that '%' operator in JS is remainder and NOT modulo
if (this.index === 0) {
this.index = this.exhibitors.length - 1
} else {
this.index = --this.index % this.exhibitors.length
}
}
},
components: {
'display': Display
}
}
</script>
So you can get what you want to happen and there are two ways of making it happen that I can think of. First I will just clarify the terms relating to this because you seem to have them the wrong way around. Let's look at you tier structure which is like this:
Control.vue
contains: Display.vue
contains: Exhibitors.vue & Buyers.vue.
Therefore Control.vue is the parent of Display.vue which is the parent of Buyers.vue and Exhibitors.vue.
Anyway, What we need to do is control the array of exhibitors (and I guess buyers but you didn't include them in your code so I'll do likewise) which is in Exhibitors.vue from Control.Vue even though they don't have a direct parent child relationship. What I've done is set a prop that is passed to Display.vue which uses scoped slots to render the exhibitors from Exhibitors.Vue. Because the left and right buttons need to know when to stop going I have emitted the array length from Exhibitors.vue to Display.vue and again to Control.vue. It all works so heres some code.
//Control.vue
<template>
<div class="content">
<display v-on:finalLength="setIndexLimit" :i="index"></display>
<button #click="changeDown">Down</button>
<button #click="changeUp">Up</button>
<p>{{indLimit}}</p>
</div>
</template>
<script>
import Display from '#/components/Display'
export default {
components: {
Display
},
data: () => ({
index: 0,
indLimit: 0
}),
methods: {
changeUp() {
if (this.indLimit === this.index+1) {
this.index=0
}
else {
this.index ++
}
},
changeDown() {
if (this.index === 0) {
this.index = this.indLimit - 1
}
else {
this.index --
}
},
setIndexLimit(e) {
this.indLimit = e
}
}
}
</script>
and
//Display.vue
<template>
<div id="display">
<p>From Display</p>
<exhibitors v-on:ExLength="setLength">
<p>{{i}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].firstName}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].lastName}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].tagNum}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].species}}</p>
</exhibitors>
</div>
</template>
<script>
import Child from '#/components/admin/Exhibitors'
export default {
components: {
Exhibitors
},
props: [
'i'
],
data: () => ({
exhibitLength: null
}),
methods: {
setLength(e) {
this.exhibitLength = e
this.$emit('finalLength',e)
}
}
}
</script>
And finally:
//Exhibitors.vue
<template>
<div>
<slot :exhibitors="exhibitors"><p>I'm the child component!</p></slot>
</div>
</template>
<script>
export default {
data: () => ({
exhibitors: [
{
firstName: 'Joe',
lastName: 'Burns',
tagNum: 1,
species: 'ant'
},
{
firstName: 'Tom',
lastName: 'Yorke',
tagNum: 2,
species: 'bee'
},
{
firstName: 'Flit',
lastName: 'Argmeno',
tagNum: 3,
species: 'giraffe'
}
],
}),
mounted: function () {
this.$nextTick(function () {
let length = this.exhibitors.length
this.$emit('ExLength', length)
})
}
}
</script>
So as I said all that works, you can click the buttons and it will loop through the array contents. You can style it how you want wherever you like. However, it is a bit messy with props and slots and emits and whatnot just to relay a single index number and it would be far easier in my opinion to have a vuex store where 'index' is stored as a state item and can be used and changed anywhere without all the above carry on.
i am still new to VUEX and i am following this tutorial vuex store
The only thing i am doing differently is i am using sequelize instead of mLab like he does.
This is what my getters look like
export const companyGetters = {
allCompanies: (state, getters) => {
return state.companies
},
companyById: (state, getters) => id => {
if (getters.allCompanies.length > 0) {
return getters.allCompanies.filter(c => c._id === id)[0]
} else {
return state.company
}
}
Exactly like what he did.
My action looks like this
companyById ({commit}, payload) {
commit(COMPANY_BY_ID)
axios.get(`${API_BASE}/companies/${payload}`).then(response => {
console.log(payload, response.data)
commit(COMPANY_BY_ID_SUCCESS, response.data)
})
}
Next in my details i have
<template>
<div>
<company-details :company="company" ></company-details>
</div>
</template>
<script>
import CompanyDetails from '../components/company/CompanyDetails'
export default {
created () {
if (!this.company.companyName) {
this.$store.dispatch('companyById', this.$route.params['id'])
}
},
computed: {
company () {
return this.$store.getters.companyById(this.$route.params['id'])
}
},
components: {
'company-details': CompanyDetails
}
}
</script>
and then finally my companyDetails looks like this
<template>
<v-ons-col style="width: 350px; float:left;">
<v-ons-card>
<h1>{{company}}</h1>
</v-ons-card>
</v-ons-col>
</template>
<script>
export default {
props: ['company']
}
</script>
here is the mutations
export const companyMutations = {
[ALL_COMPANYS] (state) {
state.showLoader = true
},
[ALL_COMPANYS_SUCCESS] (state, payload) {
state.showLoader = false
state.companies = payload
},
[COMPANY_BY_ID] (state) {
state.showLoader = true
},
[COMPANY_BY_ID_SUCCESS] (state, payload) {
state.showLoader = false
state.company = payload
}
and here is my actions
allCompanies ({commit}) {
commit(ALL_COMPANYS)
axios.get(`${API_BASE}/companies`).then(response => {
commit(ALL_COMPANYS_SUCCESS, response.data)
})
},
companyById ({commit}, payload) {
commit(COMPANY_BY_ID)
axios.get(`${API_BASE}/companies/${payload}`).then(response => {
console.log(payload, response.data)
commit(COMPANY_BY_ID_SUCCESS, response.data)
})
My CompanyList looks like this
<template>
<div>
<div class="companies">
<div class="container">
<template v-for="company in companies" >
<company :company="company" :key="company.id"></company>
</template>
</div>
</div>
</div>
</template>
<script>
import Company from './Company.vue'
export default {
name: 'companies',
created () {
if (this.companies.length === 0) {
this.$store.dispatch('allCompanies')
}
},
computed: {
companies () {
return this.$store.getters.allCompanies
}
},
components: {
'company': Company
}
}
</script>
Imported Company looks like this
<template>
<v-ons-col style="width: 350px; float:left;">
<router-link :to="'/details/'+company.id" class="company-link">
<v-ons-card>
<img :src="company.imageURL" style="width:100% ;margin: 0 auto;display: block;">
<div class="title">
<b>{{company.companyName}}</b>
</div>
<div class="description">
{{company.description}}
</div>
<div class="content">
<!-- <template v-for="company in companies">
{{company}}
</template> -->
</div>
</v-ons-card>
</router-link>
</v-ons-col>
</template>
<script>
export default {
name: 'company',
props: ['company']
}
</script>
So when i click on one of these "companies" its suppose to get it by id and show the details however in the getters this return getters.allCompanies.filter(c => c._id === id)[0] returns undefined, when i refresh the page then it gets the correct company and displays it, what is going on please help. If you need more info please ask