vue 3 + vuex cart Cannot read properties of undefined (reading 'qty')(product quantity in the cart needed to be updated) - vue.js

so I get the error: "Cannot read properties of undefined (reading 'qty')" because I try to add the same product in the cart so it's quantity(and price) will update in the cart.
when I do it from shop.vue it works and update the total price, but in the cart the same button does not work and showing me the error above.
how can I make it work? what is missing? thanks.
the code:
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';
import { mapMutations, mapState } from 'vuex';
export default {
name: 'shop',
methods: {
...mapMutations([
'addToCart',
])
},
components: {
'cart-badge': CartBadge
},
computed: {
cartLength() {
return this.$store.state.cart.length;
},
products() {
return this.$store.state.Product;
},
...mapState([
"cart"
]),
},
}
</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
}
]
store.js
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 * Product.qty);
}, 0);
},
},
mutations: {
addToCart: (state, item) => {
let product = state.Product.find((product) => product.id === item.id);
let cartProduct = state.cart.find((product) => product.id === item.id);
if (cartProduct) {
cartProduct.qty++;
} else {
state.cart.push({
...product,
stock: product.qty,
qty: 1,
});
}
product.qty--;
},
},
actions: {
addToCart(context, payload) {
context.commit("addToCart", payload);
},
},
modules: {},
});
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="addToCart()">+</button>
<button v-if="cart.length" class="decrement" #click="removeFromCart()">-</button>
<strong v-if="cart.length">Total ${{ cartSum }}</strong>
</div>
</template>
<script>
import CartBadge from '../components/CartBadge.vue';
import { mapGetters, mapState } from 'vuex';
import Product from "../db.json"
export default {
name: 'Cart',
components: {
'cart-badge': CartBadge
},
computed: {
cartLength() {
return this.$store.state.cart.length;
},
products() {
return this.$store.state.Product;
},
...mapGetters([
"cartSum"
]),
...mapState([
"cart"
]),
},
methods: {
removeFromCart(Product) {
this.$store.state.cart.splice(Product)
console.log(this.$store.state.cart)
},
addToCart() {
this.$store.dispatch("addToCart", this.products);
}
}
}
</script>
update: I have tried this: addToCart(product) { this.$store.dispatch("addToCart", product); }
but now I'm getting Uncaught TypeError: Cannot read properties of undefined (reading 'id') what am I missing?

In shop.vue you call addToCart mutation with only one product (#click="addToCart(Product)") but in cart.vue you call addToCart with an array of product (this.$store.dispatch("addToCart", this.products))

Related

vuex unknown action type: addTodo

Hi I am getting the Error like this
[vuex] unknown action type: addTodo
I am new to Vue js and now i am learing so could please help me to resolve this issue.
Code is like this
store/index.js
import { createStore } from 'vuex'
export default createStore({
state: {
todos: [
{
id: 1,
title: 'One'
},
{
id: 2,
title: 'Two'
},
{
id: 3,
title: 'Three'
},
]
},
getters: {
allTodos: (state) => state.todos,
},
mutations: {
addTodo({ commit }, todo){
commit("add_todo", todo)
}
},
actions: {
add_todo(state, todo){
state.todos.push(todo)
console.log(todo);
}
},
modules: {
}
})
and TodoInput.vue code is
<template>
<div>
<div class="row">
<input v-model="todoText" class="col form-control mx-2" type="text" />
<button #click="addTodo(todoText)" class="btn btn-primary">Add List</button>
</div>
</div>
</template>
<script>
import { mapActions } from 'vuex'
export default{
name: 'TodoInput',
data(){
return{
todoText: ''
}
},
methods:{
...mapActions(["addTodo"])
}
}
</script>
How can I solve above issue ?
Here make the below given changes in your store/index.js file:
mutations: {
add_todo: (state, todo) => state.todos.push(todo),
},
actions: {
addTodo({ commit }, todo){
commit("add_todo", todo)
}
},

Search bar filter vue 3

I just want to add a search bar filter to my vue 3 project but I don't know why is not working as good as I wanted.
Here is my code:
App.vue
<template>
<div id="app">
<div class="header">
<img class="logo" alt="UOC logo" src="./assets/uoc-logo.png" />
<div class="app-name">Recipe book</div>
</div>
<search-bar #search="setSearchTerm" />
<recipe-list :recipeList="filteredData" #delete-recipe="deleteRecipe" />
<recipe-form
v-if="showModal"
#add-recipe="addRecipe"
#close-modal="showModal = false"
/>
</div>
</template>
<script>
import RecipeList from "./components/RecipeList.vue";
import RecipeForm from "./components/RecipeForm.vue";
import SearchBar from "./components/SearchBar.vue";
import { defineComponent } from "vue";
export default defineComponent({
name: "App",
components: {
RecipeList: RecipeList,
RecipeForm,
SearchBar,
},
data: () => ({
recipeList: [
{
id: 1,
servings: 4,
time: "30m",
difficulty: "Easy",
title: "Spaghetti",
ingredients: ["noodles", "tomato sauce", "cheese"],
directions: ["boil noodles", "cook noodles", "eat noodles"],
imageUrl:
"https://imagesvc.meredithcorp.io/v3/mm/image?q=60&c=sc&poi=face&w=2000&h=1000&url=https%3A%2F%2Fstatic.onecms.io%2Fwp-content%2Fuploads%2Fsites%2F21%2F2018%2F02%2F14%2Frecetas-4115-spaghetti-boloesa-facil-2000.jpg",
},
{
id: 2,
servings: 2,
time: "15m",
difficulty: "Medium",
title: "Pizza",
ingredients: ["dough", "tomato sauce", "cheese"],
directions: ["boil dough", "cook dough", "eat pizza"],
imageUrl:
"https://www.saborusa.com/wp-content/uploads/2019/10/Animate-a-disfrutar-una-deliciosa-pizza-de-salchicha-Foto-destacada.png",
featured: true,
},
{
id: 3,
servings: 6,
time: "1h",
difficulty: "Hard",
title: "Salad",
ingredients: ["lettuce", "tomato", "cheese"],
directions: ["cut lettuce", "cut tomato", "cut cheese"],
imageUrl:
"https://www.unileverfoodsolutions.es/dam/global-ufs/mcos/SPAIN/calcmenu/recipes/ES-recipes/In-Development/american-bbq-beef-salad/main-header.jpg",
},
],
showModal: false,
recipesData: RecipeList,
filteredData: RecipeList,
}),
methods: {
deleteRecipe(id) {
this.recipeList.splice(id, 1);
},
addRecipe(recipe) {
this.recipeList.push(recipe);
},
toggleForm() {
if (this.showModal === false) {
this.showModal = true;
}
},
setSearchTerm(value) {
console.log(value);
if (value && value.length > 0) {
this.filteredData = this.recipesData.filter((i) => {
const val = value.toLowerCase();
const title = i.title && i.title.toLowerCase();
if (val && title.indexOf(val) !== -1) {
return true;
}
return false;
});
} else {
this.filteredData = this.recipesData;
}
},
},
});
</script>
SearchBar.vue
<template>
<div class="search">
<input
type="text"
placeholder="Search for a recipe"
id="search"
#change="search"
v-model="searchText"
/>
<button #click="clearSearch">Clear search</button>
<button #click="showForm">Add a new recipe</button>
</div>
</template>
<script>
import { defineComponent } from "vue";
export default defineComponent({
name: "SearchBar",
data() {
return { searchText: "" };
},
methods: {
showForm() {
console.log("show");
this.$emit("show-form");
},
clearSearch() {
this.clearInput = "";
},
search() {
this.$emit("search", this.searchText);
},
},
});
</script>
The component of search bar is working but when I acces to my website the array object is broken and I can not see any recipe. I just see recipes If I find it.
Thanks for your help

Default props value are not selected in vue3 options api

I created a select2 wrapper in vue3 with options API everything working fine but the problem is that when getting values from calling API it's not selected the default value in the select2 option. but when I created a static array of objects it does. I don't know why it's working when it comes from the API
Parent Component
Here you can I passed the static options array in options props and my selected value is 2 and it's selected in my Select2 component, but when passed formattedCompanies it's not which is the same format as the static options array then why is not selected any reason here..?
<template>
<Form #submitted="store()" :processing="submitting">
<div class="row">
<div class="col-lg-6">
<div class="form-group">
<label>Company Name</label>
<Select2
:options="options"
v-model="selected"
placeholder="Select Company"
/>
<ValidationError :errors="errors" error-key="name" />
</div>
</div>
</div>
</Form>
</template>
<script>
import Form from "#/components/Common/Form";
import Select2 from "#/components/Common/Select2";
export default {
components: {
Select2,
Form
},
data() {
return {
selected : 2,
companies : [],
options: [ // static array
{ id: 1, text: 'hello' },
{ id: 2, text: 'hello2' },
{ id: 3, text: 'hello3' },
{ id: 4, text: 'hello4' },
{ id: 5, text: 'hello5' },
],
}
},
mounted() {
this.getAllMedicineCompanies()
},
computed:{
formattedCompanies() {
let arr = [];
this.companies.forEach(item => {
arr.push({id: item.id, text: item.name})
});
return arr;
}
},
methods: {
getAllMedicineCompanies(){
axios.get('/api/get-data?provider=companies')
.then(({ data }) => {
this.companies = data
})
},
}
}
</script>
Select2 Component
Here is what my select2 component look like, did I do anything wrong here, please anybody help me
<template>
<select class="form-control">
<slot/>
</select>
</template>
<script>
export default {
name: "Select2",
props: {
options: {
type: [Array, Object],
required: true
},
modelValue: [String, Number],
placeholder: {
type: String,
default: "Search"
},
allowClear: {
type: Boolean,
default: true
},
},
mounted() {
const vm = this;
$(this.$el)
.select2({ // init select2
data: this.options,
placeholder: this.placeholder,
allowClear: this.allowClear
})
.val(this.modelValue)
.trigger("change")
.on("change", function () { // emit event on change.
vm.$emit("update:modelValue", this.value);
});
},
watch: {
modelValue(value) { // update value
$(this.$el)
.val(value)
.trigger("change");
},
options(options) { // update options
$(this.$el)
.empty()
.select2({data: options});
},
},
destroyed() {
$(this.$el)
.off()
.select2("destroy");
}
}
</script>
Probably when this Select2 mounted there is no companies. It is empty array after that it will make API call and it it populates options field and clear all options.
Make:
companies : null,
Change it to
<Select2
v-if="formattedCompanies"
:options="formattedCompanies"
v-model="selected"
placeholder="Select Company"
/>
It should be like this:
<template>
<Form #submitted="store()" :processing="submitting">
<div class="row">
<div class="col-lg-6">
<div class="form-group">
<label>Company Name</label>
<Select2
v-if="formattedCompanies"
:options="formattedCompanies"
v-model="selected"
placeholder="Select Company"
/>
<ValidationError :errors="errors" error-key="name" />
</div>
</div>
</div>
</Form>
</template>
<script>
import Form from "#/components/Common/Form";
import Select2 from "#/components/Common/Select2";
export default {
components: {
Select2,
Form
},
data() {
return {
selected : 2,
companies : null,
options: [ // static array
{ id: 1, text: 'hello' },
{ id: 2, text: 'hello2' },
{ id: 3, text: 'hello3' },
{ id: 4, text: 'hello4' },
{ id: 5, text: 'hello5' },
],
}
},
mounted() {
this.getAllMedicineCompanies()
},
computed:{
formattedCompanies() {
let arr = [];
this.companies.forEach(item => {
arr.push({id: item.id, text: item.name})
});
return arr;
}
},
methods: {
getAllMedicineCompanies(){
axios.get('/api/get-data?provider=companies')
.then(({ data }) => {
this.companies = data
})
},
}
}
</script>
The problem was that my parent component and Select2 component mounted at the same time that's why my computed value is not initialized so the selected value is not selected in the option,
problem solved by setTimeOut function in mounted like this
Select2 Component
<script>
mounted() {
const vm = this;
setTimeout(() => {
$(this.$el)
.select2({ // init select2
data: this.options,
placeholder: this.placeholder,
allowClear: this.allowClear
})
.val(this.modelValue)
.trigger("change")
.on("change", function () { // emit event on change.
vm.$emit("update:modelValue", this.value);
});
}, 500)
},
</script>

How to use an object not an array for an autocomplete form

I am new to Vue JS and having a mind blank when I've been creating my first Auto complete comp with VueCLI.
Here is the working code with an array:
https://pastebin.com/a8AL8MkD
filterStates() {
this.filteredStates = this.states.filter(state => {
return state.toLowerCase().startsWith(this.state.toLowerCase())
})
},
I am now trying to get it to work with JSON so I can use axios to get the data.
In the filterStates method I understand I need to get the name of the item and do the lowercase on that, but it keeps erroring out when I try this:
https://pastebin.com/HPYyr9QH
filterStates() {
this.filteredStates = this.states.filter(state => {
return state.name.toLowerCase().startsWith(this.state.name.toLowerCase())
})
},
Vue is erroring this:
[Vue warn]: Error in v-on handler: "TypeError: state.toLowerCase is not a function"
Do I need to pass in a key or something to identify each record?
Let's take your second pastebin :
<script>
import PageBanner from '#/components/PageBanner.vue'
export default {
components: {
PageBanner
},
data() {
return {
state: '',
modal: false,
states: [
{
id: 1,
name: 'Alaska'
},
{
id: 2,
name: 'Alabama'
},
{
id: 3,
name: 'Florida'
}
],
filteredStates: []
}
},
methods: {
filterStates() {
this.filteredStates = this.states.filter(state => {
return state.name.toLowerCase().startsWith(this.state.name.toLowerCase())
})
},
setState(state) {
this.state = state
this.modal = false
}
}
}
</script>
You are calling : this.state.name.toLowerCase().
But this.state returns '' initially. So this.state.name is undefined.
You should initialize this.state with an object :
data() {
return {
state: {
name: ''
}
...
EDIT 17/03/2020
Here is another working solution :
What I did :
state is a string again. so I check this.state.toLowerCase()
In the setState function, I just pass the name : this.state = state.name
And to fix another error I changed this line : :key="filteredState.id" because a key should not be an object
<template>
<div>
<div class="AboutUs">
<PageBanner>
<template slot="title">Search</template>
</PageBanner>
<div class="container-fluid tarms-conditions">
<div class="row">
<div class="container">
<input
id
v-model="state"
type="text"
name
autocomplete="off"
class="form-control z-10"
placeholder="Search for a state..."
#input="filterStates"
#focus="modal = true"
>
<div
v-if="filteredStates && modal"
class="results z-10"
>
<ul class="list">
<li
v-for="filteredState in filteredStates"
:key="filteredState.id"
#click="setState(filteredState)"
>
{{ filteredState }}
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import PageBanner from '#/components/PageBanner.vue'
export default {
components: {
PageBanner
},
data () {
return {
state: '',
modal: false,
states: [
{
id: 1,
name: 'Alaska'
},
{
id: 2,
name: 'Alabama'
},
{
id: 3,
name: 'Florida'
}
],
filteredStates: []
}
},
methods: {
filterStates () {
this.filteredStates = this.states.filter(state => {
return state.name.toLowerCase().startsWith(this.state.toLowerCase())
})
},
setState (state) {
this.state = state.name
this.modal = false
}
}
}
</script>

Vue URL's not working when navigating directly to a page

I have a series of products, and I'm trying to set my app up in a way that let's me send someone a link directly to a product.
Everything works fine when you try to navigate to a product directly, but if you open that same url directly (without navigating there through the app), it doesn't work.
The issue is coming from subcategoryItems being undefined in the single item view
Router snippet:
{
path: '/categories',
name: 'categories',
components: { default: Categories, header: StarterNavbar, footer: StarterFooter },
props: {
header: { colorOnScroll: 400 },
footer: { backgroundColor: 'black' }
}
},
{
path: '/categories/:catname',
name: 'products',
components: { default: Products, header: StarterNavbar, footer: StarterFooter },
props: {
header: { colorOnScroll: 400 },
footer: { backgroundColor: 'black' }
}
},
{
path: '/categories/:catname/:productname',
name: 'singleproduct',
components: { default: SingleProduct, header: StarterNavbar, footer: StarterFooter },
props: {
header: { colorOnScroll: 400 },
footer: { backgroundColor: 'black' }
}
},
Product View
<template>
<div class="">
<section class="subcategory-container" v-for="(category, index) in subcats" v-bind:key="index">
<h2>{{category.subcategoryTitle}}</h2>
<card class="card-shell" v-for="(product, index) in category.subcategoryItems" v-bind:key="index">
<div class="image-container">
<img slot="image" class="card-img-top" :src="product.image" alt="Card image cap">
</div>
<div>
<h4 class="card-title">{{product.title}}</h4>
<p class="card-text">{{product.sku}}</p>
<div>
<router-link :to="{ name: 'singleproduct', params: { productname: product.title, subcatTitle: category.subcategoryTitle } }" class="text-white">
<n-button type="primary">{{buttonText}}</n-button>
</router-link>
</div>
</div>
</card>
</section>
</div>
</template>
<script>
import { Card, Button, Modal } from '#/components';
import axios from 'axios'
export default {
name: 'products',
components: {
Card,
Modal,
[Button.name]: Button
},
async created() {
const url = this.$route.params.catname;
try {
const res = await axios.get(`/products/${url}.json`);
this.subcats = res.data;
this.catname = url;
} catch (e) {
console.log(e);
}
},
data() {
return {
subcats: [],
modals: {
classic: false
},
showModal(product) {
this.modals.classic = true;
this.selectedItem = product;
},
buttonText: "Product Info",
selectedItem: '',
catname: ''
}
}
};
single item view:
<template>
<card class="card-nav-tabs text-center" header-classes="card-header-warning">
<div slot="header" class="mt-2">
<img src="" alt="">
</div>
<h4 class="card-title">{{product.title}}</h4>
<p class="card-text">{{product.description}}</p>
<div slot="footer" class="card-footer text-muted mb-2">
{{product.sku}}
</div>
</card>
</template>
<script>
import { Card, Button } from '#/components';
import axios from 'axios';
import { async } from 'q';
export default {
name: 'singleproduct',
components: {
Card,
[Button.name]: Button
},
async created() {
const { catname, productname, subcatTitle } = this.$route.params;
//console.log(this.$route.params)
try {
const res = await axios.get(`/products/${catname}.json`);
const data = res.data;
const items = data.find(product => product.subcategoryTitle === subcatTitle).subcategoryItems;
const item = items.find(item => item.title === productname);
console.log(item);
this.product = item;
} catch (e) {
console.log(e);
}
},
data () {
return {
product: []
}
}
}
</script>
Json sample:
[
{
"subcategoryTitle": "sub cat title 1",
"subcategoryItems": [
{
"id": 1,
"title": "name 1",
"sku": "sku 1",
"image": "img/path to image",
"description": "Cream beans medium rich breve cinnamon latte. White pumpkin spice kopi-luwak sugar foam frappuccino dark. Brewed arabica, dripper arabica as milk turkish medium."
}
]
},
{
"subcategoryTitle": "sub cat title 2",
"subcategoryItems": [
{
"id": 1,
"title": "name 2",
"sku": "sku 2",
"image": "img/path to image"
"description": "Cream beans medium rich breve cinnamon latte. White pumpkin spice kopi-luwak sugar foam frappuccino dark. Brewed arabica, dripper arabica as milk turkish medium."
},
{
"id": 2,
"title": "name 2",
"sku": "sku 2",
"image": "img/path to image",
"description": "Cream beans medium rich breve cinnamon latte. White pumpkin spice kopi-luwak sugar foam frappuccino dark. Brewed arabica, dripper arabica as milk turkish medium."
}
]
}
]
Thank you
This requires history mode enabled in the router. The router documentation has an explanation and example for this:
https://router.vuejs.org/guide/essentials/history-mode.html
If you look at the code I posted, the issue was that I was missing a slug in the URL for the final product page.
When I navigated there through the app, the product info was all there, but on reload, it didn't match the route anymore, so the details vanished.