The Vuex storage has a getter which returns object by id.
But I can't use the data from the getter.
When i use computed: data come from getter after the component was rendered, and DOM still empty (until I'm not clicking on component in vue devtools. If i click, computed run again, and element are redrawn and filled with the data from data())
if I call the getter directly during the definition of data() objects, {{ cardData.content }} in DOM can be seen immediately, but this.cardData.title returns an error Cannot read property 'title' of undefined. Why?
Vuex storage
export const store = new Vuex.Store({
state: {
"cards": [
{
"position": null,
"title": "Head Text",
"id": 132,
"content": "LoremIpsum",
"type": "text",
},
{
"position": null,
"title": "Head Text 2",
"id": 138,
"content": "LoremIpsumLoremIpsum",
"type": "text",
}
]
},
getters: {
cards: (state) => state.cards
}
});
vue component,
<template>
<div>
<h1>{{ cardTitle }}{{ cardTitle2 }}</h1>
<div>{{ cardData.content }}</div>
</div>
</template>
<script>
export default {
props: ['id'],
data() {
return {
cardData: this.$store.getters.cards.find((card) => card.id == this.id),
cardTitle: this.cardData.title,
cardData2: null,
cardTitle2: null
}
},
computed: {
GetData: function() {
this.cardData2 = this.$store.getters.cards.find((card) => card.id == this.current);
this.cardTitle2 = this.cardData2.title;
},
}
}
</script>
Why not just have a computed property which returns desired title, it can be just following as documented here:
computed: {
cardTitle: function() {
var card = this.$store.getters.cards.find((card) => card.id == this.current)
return card.title
},
}
Now you can use {{this.cardTitle}} in the DOM.
Getting Vuex State into Vue Components
So how do we display state inside the store in our Vue components?
Since Vuex stores are reactive, the simplest way to "retrieve" state
from it is simply returning some store state from within a computed
property
In your case you have card array in vuex state, and you use data method to return array. That is not proper way to handle this case. You need to use computed method as a getter. Whenever this.$store.state.card changes, it will cause the computed property to re-evaluate, and trigger associated DOM updates:
computed: {
cardData() {
return this.$store.getters.cards
}
}
And in your component template you can display properties from cardData array, whether it needs to be title, content, type, id...etc. You don't need to create cardTitle, cardId, cardType variables for every property in array, just add property after cardData:
<template>
<div class="card">
<h1>{{ cardData.title }}</h1>
<p>{{ cardData.content }}</p>
</div>
</template>
Here is jsFiddle example for your case, filtered cardData array by id.
It is more efficient if you loop through the array using v-for as indicated in the vue.js documentation homepage.
computed: {
cards () {
return this.$store.getters.cardData
}
}
this.$store.getters.cardData in computed being what you have initialised in getters: i.e
getters: {
cardData: (state) => state.cards
}
and state like so:
state: {
cards: []
}
then in your template tag, you can loop through the dataset array like so:
<template id="insert id here">
<ul v-for"card in cards" :key="card.id">
<li> {{ card.title }}</li>
<li> {{ card.content }}</li>
</ul>
</template>
Related
I'm trying to pass an array of object to a childComponent as prop but when I add an object in it, it doesn't render. (Note: I'm working on vuejs 2.6)
I suppose it has a link with the "monitoring" of the items of the array and not the array itself? Stuff is that if I do not pass the prop and use the default value instead, it's working perfectly. I think I'm missing something here. Could someone help me ?
By curiosity is this kind of behavior still stand with vue3js ?
As you can see below:
App.vue:
<template>
<div id="app">
<Card
v-for="user in users"
:key="user.userId"
:userId="user.userId"
:username="getUsernameFromUserId(user.userId)"
:links="getUserLinksFromUserId(user.userId)"
/>
</div>
</template>
<script>
import Card from "./components/Card.vue";
export default {
name: "App",
components: {
Card,
},
data: function () {
return {
users: [
{ userId: 1, name: "Bob" },
{ userId: 2, name: "Alice" },
{ userId: 3, name: "Eliot" },
],
links: [
{ userId: 1, link: "hello->world" },
{ userId: 1, link: "world->!" },
{ userId: 3, link: "hello->back" },
{ userId: 4, link: "hello->you" },
],
};
},
methods: {
getUsernameFromUserId: function (userId) {
return this.users.filter((obj) => obj.userId == userId)?.[0]?.name ?? "Not found";
},
getUserLinksFromUserId: function (userId) {
return this.links.filter((obj) => obj.userId == userId);
},
},
};
</script>
Card.vue
<template>
<div class="card">
<h1>{{ username }}</h1>
<button #click="addLink">Add One link</button><br><br>
<span v-if="links.length == 0">No links</span>
<div class="links">
<Link v-for="link in links" :key="links.indexOf(link)" :link="link"></Link>
</div>
</div>
</template>
<script>
import Link from '../components/Link'
export default {
components:{Link},
props: {
userId: Number,
username: String,
links: { type: Array, default: () => [], required: false },
},
methods:{
addLink: function(){
this.links.push({
userId: this.userId,
link: 'newlink->cool'
});
}
}
}
</script>
Link.vue
<template>
<div>
<span>UserId: {{ this.link.userId }} Link: {{ this.link.link }</span>
</div>
</template>
<script>
export default {
props: {
link: { type: Object, default: () => [], required: false },
},
};
</script>
This is a bad way to work with props
Note: do not focus on Dev Tools too much as it can be "buggy" at times - especially if you use Vue in a wrong way. Focus on your app output
Your Card.vue component is modifying (push) a prop, which is not recommended but it sort of works if the prop is object/Array and you do not replace it, just modify it's content (as you do)
But in your case, the values passed to props are actually generated by a method! The getUserLinksFromUserId method is generating a new array every time it is called, and this array is NOT reactive. So by pushing to it, your component will not re-render and what is worse, parent's links array is not changed at all! (on top of that - if App.vue ever re-renders, it will generate new arrays, pass it to pros and your modified arrys will be forgoten)
So intead of modifying links prop in Card.vue, just emit an event and do the modification in App.vue
Getting an infinite loop error when looping through <a> attributes in vue.js.
I have a method that loops through and adds attributes dynamically but when I use the method by binding it to an attribute in the <a> I get the error from the title. The attributes are a nested object within the original products array of objects.
Vue Code
<template>
<div>
<p>
<a
v-for="product in products"
:href="product.product_url"
type="submit"
v-bind:additionalAttrs="addAttributes()"
>
Click Me
</a>
</p>
</div>
</template>
<script>
export default {
data () {
return {
addedAttributes: [],
};
},
props: {
products: Array,
},
methods: {
addAttributes() {
this.products.forEach(product => {
for (const [key, value] of Object.entries(product.attributes)) {
this.addedAttributes.push(`${key}: ${value}`);
}
});
}
}
}
</script>
You use a method call to pass its result to additionalAttrs prop but it's not reactive and potentially can be called as many times as you have elements in products array
You just need one computed prop instead of an array and a method because they simply depend on products prop:
<a
v-for="product in products"
:href="product.product_url"
type="submit"
v-bind:additionalAttrs="addedAttributes"
>
Click Me
</a>
computed: {
addedAttributes() {
const addedAttributes = []
this.products.forEach(product => {
for (const [key, value] of Object.entries(product.attributes)) {
addedAttributes.push(`${key}: ${value}`);
}
});
return addedAttributes
}
}
I am a beginner to Vue JS. I have to use a variable inside a component whose value changes often.
So when I declare and define it under data() the following warn is coming in Chrome console
Since when there is a change in data() variables automatically Vue framework calls render function.
Is there any way to declare and use a variable other than declaring it in data() method ??
<template>
<ul>
<div v-for="(list,index) in itemlist" :key="index">
<div v-if="!isFirstCharSame(list.label)" >{{ firstChar }} </div>
<li>
<span>{{ list.label }}</span>
</li>
</div>
</ul>
</template>
<script>
export default {
data() {
return {
itemlist: [
{"label":"Alpha"},
{"label":"Beta"},
{"label":"Charlie"},
{"label":"Delta"}],
firstChar:"$"
}
},
methods : {
isFirstCharSame: function(str) {
if(str.startsWith(this.firstChar)) {
return true;
}
this.firstChar = str.charAt(0);
return false;
}
}
}
</script>
Expected output should be like this
Inside Group A It should display all the elements starting with A
Below we will render using a computed property to make sure its sorted alphabetically and then render your first char. Though You should be using grouping imo.
<template>
<ul>
<div v-for="(list, index) in sortedlist" :key="`people_${index}`">
<div v-if="!isFirstCharSame(list.label)" >{{ firstChar }} </div>
<li>
<span>{{ list.label }}</span>
</li>
</div>
</ul>
</template>
<script>
export default {
data() {
return {
itemlist: [
{"label":"Alpha"},
{"label":"Beta"},
{"label":"Charlie"},
{"label":"Delta"},
],
firstChar: '',
};
},
methods: {
isFirstCharSame(char) {
if (str.startsWith(this.firstChar)) {
return true;
}
this.firstChar = str.charAt(0);
return false;
},
},
computed: {
sortedList() {
return this.itemList.sort((a, b) => {
if (a.label > b.label) {
return 1;
}
if (b.label > a.label) {
return -1;
}
return 0;
});
},
},
};
</script>
And yes, You can update your data any time you wish and the component will do a re render to reflect it.
You can declare variables in your component within your methods or inside computed properties, etc., but they won't be reachable from the template or the rest of the code nor they would be reactive.
The only way for them to be reactive and reachable from the higher scope is adding the data property to the component in the following way:
data: function () {
return {
foo: 'bar'
}
},
or
data () {
return {
foo: 'bar'
}
},
Besides this, the reason of your error is that you are mutating the state of your variables inside the render. When this happens, Vue re-renders the template because the values have mutated and calls again to the function and voilĂ : there you have an infinite loop.
You should probably check the function you are calling and try to replace the changing variables from the data property with local variables that take their data from the actual data variables.
I'm trying to create a simple card component CustomCard.vue and re-use it on HomeComponent.vue page with specified data, so I've created a loop and put the needed data in cards: []
I don't know why it doesn't work. I can see the 3 elements on the page but they are displayed with the default values of the component, instead of taking the data from the cards:[].
HomeComponent.vue:
...
<custom-card v-for="n in cards" :key="n">
<img :src="n.cardImage" alt="">
<p>{{n.cardDesc}}</p>
</custom-card>
...
<script>
export default {
data() {
return {
cardImage: "",
cardDesc: "",
cards: [
{id: "1", cardImage: "../src/assets/img1.jpg", cardDesc: "some description 1"},
{id: "2", cardImage: "../src/assets/img2.jpg", cardDesc: "some description 2"},
{id: "3", cardImage: "../src/assets/img3.jpg", cardDesc: "some description 3"}
]
}
}
}
</script>
CustomCard.vue:
<template>
<div>
<img :src="cardImage" alt="">
<p>{{cardDesc}}</p>
</div>
</template>
<script>
export default {
data () {
return {
cardImage: "../src/assets/default.jpg",
cardDesc: "default description text"
}
}
// props: ['cardDesc', 'cardImage']
}
</script>
I want these values below to be the default component values just as a placeholder (so I've put them in components data):
cardImage: "../src/assets/default.jpg",
cardDesc: "default description text"
If I pass the props out, I get an error: [Vue warn]: The data property "cardImage" is already declared as a prop. Use prop default value instead.
So I commented it out for now.
I've registered the CustomCard.vue globally in index.js:
Your component has no slots so there's no reason to include any content.
Seems to me you just need
<custom-card v-for="card in cards" :key="card.id"
:card-desc="card.cardDesc"
:card-image="card.cardImage" />
Note the key is set to the actual unique identifier for each iterable element.
You should of course un-comment the props declaration and remove the conflicting data keys from your component. As mentioned in the error message, you can also set default values
export default {
props: {
cardDesc: {
type: String,
default: 'default description text'
},
cardImage: {
type: String,
default: '../src/assets/default.jpg'
}
},
data () { // probably don't even need a data property
return {}
}
}
I have a component with some form validation. It is a multi step checkout form. The code below is for the first step. I'd like to validate that the user entered some text, store their name in the global state and then send then to the next step. I am using vee-validate and vuex
<template>
<div>
<div class='field'>
<label class='label' for='name'>Name</label>
<div class="control has-icons-right">
<input name="name" v-model="name" v-validate="'required|alpha'" :class="{'input': true, 'is-danger': errors.has('name') }" type="text" placeholder="First and Last">
<span class="icon is-small is-right" v-if="errors.has('name')">
<i class="fa fa-warning"></i>
</span>
</div>
<p class="help is-danger" v-show="errors.has('name')">{{ errors.first('name') }}</p>
</div>
<div class="field pull-right">
<button class="button is-medium is-primary" type="submit" #click.prevent="nextStep">Next Step</button>
</div>
</div>
</template>
<script>
export default {
methods: {
nextStep(){
var self = this;
// from baianat/vee-validate
this.$validator.validateAll().then((result) => {
if (result) {
this.$store.dispatch('addContactInfoForOrder', self);
this.$store.dispatch('goToNextStep');
return;
}
});
}
},
computed: {
name: function(){
return this.$store.state.name;
}
}
}
</script>
I have a store for handling order state and recording the name. Ultimately I would like to send all of the info from multi step form to the server.
export default {
state: {
name: '',
},
mutations: {
UPDATE_ORDER_CONTACT(state, payload){
state.name = payload.name;
}
},
actions: {
addContactInfoForOrder({commit}, payload) {
commit('UPDATE_ORDER_CONTACT', payload);
}
}
}
When I run this code I get an error that Computed property "name" was assigned to but it has no setter.
How do I bind the value from the name field to the global state? I would like this to be persistent so that even if a user goes back a step (after clicking "Next Step") they will see the name they entered on this step
If you're going to v-model a computed, it needs a setter. Whatever you want it to do with the updated value (probably write it to the $store, considering that's what your getter pulls it from) you do in the setter.
If writing it back to the store happens via form submission, you don't want to v-model, you just want to set :value.
If you want to have an intermediate state, where it's saved somewhere but doesn't overwrite the source in the $store until form submission, you'll need to create such a data item.
It should be like this.
In your Component
computed: {
...mapGetters({
nameFromStore: 'name'
}),
name: {
get(){
return this.nameFromStore
},
set(newName){
return newName
}
}
}
In your store
export const store = new Vuex.Store({
state:{
name : "Stackoverflow"
},
getters: {
name: (state) => {
return state.name;
}
}
}
For me it was changing.
this.name = response.data;
To what computed returns so;
this.$store.state.name = response.data;
I've had such an error when getting value from the store, in computed, via ...mapState(['sampleVariable']), as you. Then I've used the this.sampleVariable in <script> and sampleVariable in <template>.
What fixed the issue was to return this in data(), assign it to a separated variable, and reuse across the component the newly created variable, like so:
data() {
return {
newVariable: this.$store.state.sampleVariable,
}
}
Then, I've changed references in the component from sampleVariable to newVariable, and the error was gone.
I was facing exact same error
Computed property "callRingtatus" was assigned to but it has no setter
here is a sample code according to my scenario
computed: {
callRingtatus(){
return this.$store.getters['chat/callState']===2
}
}
I change the above code into the following way
computed: {
callRingtatus(){
return this.$store.state.chat.callState===2
}
}
fetch values from vuex store state instead of getters inside the computed hook