Vue state variable not updating - vue.js

I have this code but when the selectCategory method gets called i don’t see product.categories being updated in the vue dev console.
<li class="border hover:bg-blue-100" v-bind:class=""
v-on:click="selectCategory($event, category.id)"
>
{{category.name}}
</li>
export default {
data () {
return {
product: {
categories: []
},
}
},
methods: {
selectCategory(event, id){
this.product.categories.push(id);
},
}

This might help you as referance.
<template>
<div>
<li v-for="category in Available.categories"
class="border hover:bg-blue-100"
v-bind:class=""
v-on:click="selectCategory($event, category.id)"
>
{{category.name}}
</li>
</div>
</template>
<script>
export default{
data () {
return {
Available: {
categories: [{
"name":"Fruites",
"id":1,
},
{
"name":"Veges.",
"id":2,
}]
},
product: {
categories: []
},
}
},
methods: {
selectCategory(event, id){
this.product.categories.push(id);
console.log(this.product.categories)
},
}
}
</script>
https://codesandbox.io/s/vue-template-v2zss?fontsize=14

Related

how to not get duplicate products in cart in vue3?

so I'm building a shop in vue3 and now when I add a product to the cart it added, but if I add the same product again it shows twice(a "duplicate"). and I want it to have a increment and decrement so I will update the quantities of the product that way. how can I do that? when I try I get this error: "Uncaught TypeError: Cannot read properties of undefined (reading 'id')" How can I make it the way I want? Thanks in advance and the code is below:
btw, I'm using Vuex for state management. and the error is because of the count2 method in the cart.vue file.
shop.vue
<template>
Cart Items: <cart-badge :count="cartLength">{{ count }}</cart-badge>
<div class="shop">
<div class="products" v-for="Product in products" :key="Product.id">
<h1>{{ Product.name }}</h1> <img :src="Product.pic" width="400" /> <br>
{{ Product.description }} <br>
{{ "$" + Product.price }} <br>
<button class="addToCart" #click="addToCart(Product)">Add to Cart</button>
</div>
</div>
</template>
<script>
import CartBadge from '../components/CartBadge.vue';
export default {
name: 'shop',
methods: {
addToCart(Product) {
this.$store.state.cart.push(Product)
console.log(this.$store.state.cart)
},
},
components: {
'cart-badge': CartBadge
},
computed: {
cartLength() {
return this.$store.state.cart.length;
},
products() {
return this.$store.state.Product;
},
cart() {
return this.$store.state.cart;
}
},
/*props: {
count: Number
}*/
}
</script>
db.json
[
{
"id": 1,
"name": "iphone 11",
"pic": "/pics/iphone11.jpg",
"description": "newest iphone!",
"price": 999,
"qty": 10
},
{
"id": 2,
"name": "galaxy s22",
"pic": "/pics/galaxy22.jpg",
"description": "a new phone from samsung!",
"price": 1200,
"qty": 10
}
]
cart.vue
<template>
<h1>Cart</h1>
<div class="cartItems">
Cart Items: <cart-badge :count="cartLength">{{ count }}</cart-badge>
<p v-if="!cart.length">Your cart is empty! please add something to the cart</p>
<div class="products" v-for="Product in cart" :key="Product.id">name: {{ Product.name }} price: {{ Product.price
}}</div>
<button v-if="cart.length" class="removeFromCart" #click="removeFromCart(Product)">X</button>
<button v-if="cart.length" class="increment" #click="count2(Product)">+</button>
<button v-if="cart.length" class="descrement" #click="count--">-</button>
<strong v-if="cart.length">Total ${{ cartSum }}</strong>
<p>{{ count }}</p>
</div>
</template>
<script>
import CartBadge from '../components/CartBadge.vue';
import { mapGetters } from 'vuex';
export default {
name: 'Cart',
data() {
return {
count: 0
}
},
components: {
'cart-badge': CartBadge
},
computed: {
cartLength() {
return this.$store.state.cart.length;
},
products() {
return this.$store.state.Product;
},
cart() {
return this.$store.state.cart;
},
...mapGetters([
"cartSum"
]),
/*productsqty() {
//Product.qty = Product.qty + 1
return this.$store.state.Product.qty + 1
}*/
},
methods: {
removeFromCart(Product) {
this.$store.state.cart.splice(Product)
console.log(this.$store.state.cart)
},
count2(Product) {
/*Product = this.$store.state.cart.push(Product)
console.log(this.$store.state.cart.push(Product))*/
//Product = this.$store.state.cart(Product)
let FoundProduct = this.$store.state.cart.find(o => o.id === this.$store.state.cart.Product.id)
if (FoundProduct) {
FoundProduct.qty += 1;
return;
}
//this.cart.id = Product.id
//this.cart.push(this.cart.Product)
this.$store.state.cart.Product.id = Product.id
this.$store.state.cart.push(this.$store.state.cart.Product)
}
}
}
</script>
store.js(vuex file for state management)
import { createStore } from "vuex";
import Product from "../db.json";
export default createStore({
state: {
cart: [],
Product,
},
getters: {
cart: (state) => state.cart,
cartSum: (state) => {
return state.cart.reduce((total, Product) => {
return (total += Product.price * 1);
}, 0);
},
},
mutations: {},
actions: {},
modules: {},
});

Error: Do not mutate vuex store state outside mutation handlers

Scenario
I am using Vuex, to store some data in it, and in my case the ticket details.
Initially, I have a ticket which has an array of discounts, to be empty.
Once I hit the button "Add discount" I mount the component called "testDiscount" which in the mounted hook pushes the first object ({"code": "Foo", "value":"Boo"}) in the discounts array of a specific ticket in the store.
The problem arise when I try to type in the input boxes (changing its state) in this component where I get the error "do not mutate Vuex store state outside mutation handlers.". How could I best handle this?
Test.vue
<template>
<div>
<test-component v-for="(t, key) in tickets" :key="key" :ticket-key="key" :tid="t.id"></test-component>
</div>
</template>
<script>
import TestComponent from "~/components/testComponent.vue";
export default {
layout: "noFooter",
components: {
"test-component": TestComponent,
},
data() {
return {
tickets: this.$store.state.ticketDiscount.tickets,
};
},
mounted() {
if (this.tickets.length == 0) {
this.$store.commit("ticketDiscount/addTicket", {
id:
this.$store.state.ticketDiscount.tickets.length == 0
? 0
: this.$store.state.ticketDiscount.tickets[
this.$store.state.ticketDiscount.tickets.length - 1
].id + 1,
discount: [],
});
}
},
};
</script>
ticketDiscount.js
export const state = () => ({
tickets: []
});
export const mutations = {
addTicket(state, ticket) {
state.tickets.push(ticket);
},
addDiscount(state, property) {
state.tickets.find(ticket => ticket.id == property.id)[property.name].push(property.value);
}
testComponent.vue
<template>
<div>
<h3>Ticket number: {{ticketKey + 1}}</h3>
<button #click="showDiscount = true">Add discount</button>
<test-discount v-model="discount_" v-if="showDiscount" :tid="tid"></test-discount>
</div>
</template>
<script>
import testDiscount from "~/components/test-discount.vue";
export default {
components: {
testDiscount,
},
data() {
return {
showDiscount: false,
tid_: this.tid,
};
},
props: {
tickets: Array,
ticketKey: { type: Number },
tid: { type: Number, default: 0 },
},
methods: {
updateTicket() {
this.$emit("updateTicket", {
id: this.tid_,
value: {
discount: this.discount_,
},
});
},
},
mounted() {
this.$watch(
this.$watch((vm) => (vm.discount_, Date.now()), this.updateTicket)
);
},
computed: {
discount_: {
get() {
return this.$store.state.ticketDiscount.tickets.find(
(ticket) => ticket.id == this.tid
)["discount"];
},
set(value) {
// set discount
},
},
},
};
</script>
testDiscount.vue
<template>
<div class="container">
<div class="title">
<img src="~/assets/svgs/price_tag.svg" />
<span>Discount code</span>
{{ discounts }}
</div>
<div class="discount-container">
<div v-for="(c,idx) in discounts" class="discounts" :key="idx">
<div class="perc-input">
<input style="max-width: 50px;" v-model.number="c.discount" type="number" min="1" max="100" step="1" placeholder="10">
<div>%</div>
</div>
<input class="code-input" v-model="c.code" placeholder="Code">
<img src="~/assets/svgs/bin.svg" title="Delete code" #click="deleteCode(idx)" v-if="discounts.length > 1"/>
</div>
</div>
<span #click="newDiscount" class="add-another">+ Add another discount</span>
</div>
</template>
<script>
export default {
props: {
value: {
type: Array,
},
tid: { type: Number, default: 0 },
},
data() {
return {
discounts: this.value,
}
},
mounted() {
if (this.discounts.length == 0) {
this.newDiscount();
}
},
methods: {
newDiscount() {
this.$store.commit('ticketDiscount/addDiscount',
{
"id": this.tid,
"name": "discount",
"value": { code: null,discount: null }
});
},
deleteCode(index) {
this.discounts.splice(index, 1);
}
},
watch: {
discounts() {
this.$emit('input', this.discounts)
}
},
beforeDestroy() {
this.$emit('input', []);
}
}
</script>
you shouldn't use v-model in this case.
<input style="max-width: 50px;" v-model.number="c.discount" .../>
you could just set the value
<input style="max-width: 50px;" :value="c.discount" #change="handleValueChange" .../>
and then in handleValueChange function to commit the action to update just for that value.

Can a Vue component behave like wordpress shortcode attributes

Can a Vue component behave like wordpress shortcode attributes? For example in [wp-shortcode post_type="post"], I can define the post type in the shortcode and the data will be fetched accordingly.
Can something similar be done in Vue? For example <grid-one type="posts" /> or <grid-one type="videos" />. The type in the grid-one tag will change the request.type in the data.
GridOne.vue
<template>
<main class="site-content">
<div class="container">
<section v-if="posts.length" class="articles-list">
<post-item
v-for="post in posts"
:key="post.id"
:post="post"
/>
<pagination
v-if="totalPages > 1"
:total="totalPages"
:current="page"
/>
</section>
</div>
</main>
</template>
<script>
import PostItem from '#/components/template-parts/PostItem'
import Pagination from '#/components/template-parts/Pagination'
export default {
name: 'GridOne',
components: {
PostItem,
Pagination
},
props: {
page: {
type: Number,
required: true
}
},
data() {
return {
request: {
type: 'posts',
params: {
per_page: this.$store.state.site.posts_per_page,
page: this.page
},
showLoading: true
},
totalPages: 0
}
},
computed: {
posts() {
return this.$store.getters.requestedItems(this.request)
}
},
methods: {
getPosts() {
return this.$store.dispatch('getItems', this.request)
},
setTotalPages() {
this.totalPages = this.$store.getters.totalPages(this.request)
}
},
created() {
this.getPosts().then(() => this.setTotalPages())
}
}
</script>

Set css class for single items in v-for loop

I am playing around with Vue.js and I am trying to change the class of individual items in a v-for route dependent on a checkbox.
<template>
<div>
<ul>
<div :class="{completed: done}" v-for="things in items">
<h6 v-bind:key="things"> {{things}} - <input #click="stateChange" type="checkbox"/></h6>
</div>
</ul>
</div>
</template>
<script>
export default {
name: 'ItemList',
data() {
return {
items: [],
done: false
}
},
mounted() {
Event.$on('itemAdded', (data) => {
this.items.push(data);
})
},
methods: {
stateChange() {
this.done = !this.done;
}
}
}
</script>
<style>
.completed {
text-decoration-line: line-through;
}
</style>
The above code places a line through every item, not just the checked one.
How do I code so only the checked item is crossed out?
Thanks
Paul.
It looks like you have only one done property. You should have a done property for each element in your items array for this to work. Your item should like {data: 'somedata', done: false }
This should work:
<template>
<div>
<ul>
<div :class="{completed: item.done}" v-for="(item,index) in items">
<h6 v-bind:key="things"> {{item.data}} - <input #click="stateChange(item)" type="checkbox"/></h6>
</div>
</ul>
</div>
</template>
<script>
export default {
name: 'ItemList',
data() {
return {
items: [],
}
},
mounted() {
Event.$on('itemAdded', (data) => {
this.items.push({ data, done: false });
})
},
methods: {
stateChange(changeIndex) {
this.items = this.items.map((item, index) => {
if (index === changeIndex) {
return {
data: item.data,
done: !item.done,
};
}
return item;
});
}
}
}
</script>
<style>
.completed {
text-decoration-line: line-through;
}
</style>
#Axnyff
You were very close. Thank you. Here is the little changes I made to get it working.
<template>
<div>
<ul>
<div :class="{completed: item.done}" v-for="item in items">
<h6> {{item.data}} - <input #click="item.done = !item.done" type="checkbox"/></h6>
</div>
</ul>
</div>
</template>
<script>
export default {
name: 'ItemList',
data() {
return {
items: [],
}
},
mounted() {
Event.$on('itemAdded', (data) => {
this.items.push({ data, done: false });
console.log("DATA- ", this.items)
})
},
methods: {
}
}
</script>
<style>
.completed {
text-decoration-line: line-through;
}
</style>

Filter array results on keyup

I have an array of names that I loop over using v-for I'm trying to filter these results when a user starts typing in a search box.
I've added my code below for reference if I do my loop as v-for="entry in entries" then it output the array but doesn't work with the computed and filteredList function
<template>
<div class="container-flex">
<div class="entries">
<div class="entries__header">
<div class="entries__header__title">
<p>Entries</p>
</div>
<div class="entries__header__search">
<input
type="text"
name="Search"
class="input input--search"
placeholder="Search..."
v-model="search">
</div>
</div>
<div class="entries__content">
<ul class="entries__content__list">
<li v-for="entry in filteredList">
{{ entry.name }}
</li>
</ul>
</div>
</div>
</div>
</template>
<script>
import addEntry from '#/components/add-entry.vue'
export default {
name: 'entry-list',
search: '',
components: {
addEntry
},
data: function() {
return {
entries: [
{
name: 'Paul'
},
{
name: 'Barry'
},
{
name: 'Craig'
},
{
name: 'Zoe'
}
]
}
},
computed: {
filteredList() {
return this.entries.filter(entry => {
return entry.name.toLowerCase().includes(this.search.toLowerCase())
})
}
}
}
Try to move the search prop in to data option like this:
export default {
name: 'entry-list',
components: {
addEntry
},
data: function() {
return {
search: '',
entries: [
{
name: 'Paul'
},
{
name: 'Barry'
},
{
name: 'Craig'
},
{
name: 'Zoe'
}
]
}
},
computed: {
filteredList() {
if(this.search === '') return this.entries
return this.entries.filter(entry => {
return entry.name.toLowerCase().includes(this.search.toLowerCase())
})
}
}
}
Also add a check if the search prop is empty to return the full entries list.
Demo fiddle