Vue/Vuex Accessing Objects elements - vue.js

Hi I am getting confused as to how I can access some data within my Object. I am using Vuex and I have a standard page. Here, I use a getter to obtain the Payment
Object and pass it to a component.
<template>
<div>
<div class="container">
<div class="row">
<div class="col-12 flex">
<payment-details :payment="payment"></payment-details>
</div>
</div>
</div>
</div>
</template>
<script>
import {
PaymentDetails
} from "#/components/Payment";
export default {
components: {
'payment-details': PaymentDetails
},
created () {
if (!this.payment.paymentID) {
this.$store.dispatch('paymentById', this.$route.params['id'])
}
},
computed: {
payment () {
return this.$store.getters.paymentById(this.$route.params['id'])
}
}
}
</script>
Now within the component, within the template, I can do something like this
<template>
<div v-if="payment">
<div class="row">
<div class="col-12">
<h3 class="h-100">{{ payment.details }}</h3>
</div>
</div>
</div>
</template>
This all works fine. However, within this component, I need to access some elements of the payment object. This is where I get confused, if I create a mounted or created hook and do this
created() {
console.log(this.payment)
}
I always get an Observer object. If I try accessing an element from this Object e.g.
created() {
console.log(this.payment.details)
}
I get undefined. I basically have a slideshow I am creating in a mounted hook. I need to push some items contained within this Object onto the slideshow array.
So how can I actually get access to the elements of this Object?
Thanks

You should use watcher on your vuex object.
Here is link to documentation https://v2.vuejs.org/v2/guide/computed.html#Computed-vs-Watched-Property
Most probably your this.payment.details is instantiated after your created method was called.
Move your code from created method to:
export default {
watch: {
payment: function (val) {
console.log('-------- this is this.payment.details:');
console.log(val.details);
},
...

Yes it will gave you of undefined because in your props you declare only a payment object alone not like this one below.
payment : {
details: '',
etc: ''
}
But it will still works when you use this payment data in your component, it's like it only gives you an error something like 'calling details on null' like that. I prefer to put condition first if payment has already data before you call in your component. Like below
<div v-if="payment.details">{{payment.details}}</div>

Related

passing object to component using v-for

I am trying to send a series of objects that are in an array to a child component using v-for, but when I try to access them from the child component, it tells me that the props are not defined.
Im using Quasar Framework actually
This is how I pass the data:
<div class="row justify-center">
<foo
v-for="brand in brands"
:key="brand.id"
:brand="brand"
></foo>
</div>
<script>
import foo from "src/components/foo.vue";
export default {
components: {
foo
},
data() {
return {
brands: []
};
},
methods: {
async getData() {
let x = await get.getData();
this.brands = x.data;
console.log(this.brands);
}
},
mounted() {
this.getData();
}
};
</script>
brands is an array that obtains two objects from a request made to a local database, which I have already verified that it receives the data correctly
And this is the component file and how I try to get the properties:
<q-card class="my-card" flat bordered>
<q-img
:src="require(`../assets/${brand.img}`)"
:alt="brand.img + ' Logo'"
/>
<div class="text-h5 q-mt-sm q-mb-xs">{{ brand.name }}</div>
<div class="text-caption text-grey">
<p>
{{ brand.price }}
</p>
</div>
<script>
export default {
name: "foo",
props: ["brand"],
data() {
return {
expanded: false
};
},
};
</script>
but when I try to execute the code it gives me the following error:
Error in render: "Error: Cannot find module './undefined'
I know one way to make it work, and it is by creating a property for each of the object's values, for example:
<component
v-for="brand in brands"
:key="brand.id"
:name="brand.name"
:price="brand.price"
></component>
But I dont think thats the correct way to do this....
try to change
import component from "src/components/component.vue";
to
import foo from "src/components/component.vue";
on your components section you just call foo instead of foo:component
I am not sure, but:
Looks like ${brand} is empty. Your function GetData() is async, so the <foo> is created before the GetData() has its data set/returned.
You can change
<foo v-for="brand in brands" :key="brand.id" :brand="brand"></foo>
To
<foo v-if="brands.length> 0" v-for="brand in brands" :key="brand.id" :brand="brand"></foo>
To make sure that the element is renderd after the data if set.
Note: v-if is when the html is rendered, v-show is just a css display hide, but the html is always renderd

Vue.js 2: How to bind to a component method?

I have a VueJS (v2) component with a private array of objects this.private.messagesReceived which I want displayed in a textarea. The array should be converted to a string by a method/function and Vue is blocking all my attempts to bind. Every attempt results in my serialization function (converting the array to a string) only being called once and never again when the data changes.
I feel there must be a way to do this without Vue.set() or some forceUpdate shenanigans.
https://jsfiddle.net/hdme34ca/
Attempt 1: Computed Methods
Here we have the problem that Vue only calls my computed method messagesReceived1 once and never again.
<script>
{
computed: {
messagesReceived1() {
console.log("This is called once and never again even when new messages arrive");
return this.private.messagesReceived.join("\n");
},
...
methods: {
addMessage(m) {
console.log("This is called multiple times, adding messages successfully");
this.private.messagesReceived.push(m);
}
}
<script>
<template>
<textarea rows="10" cols="40" v-model="messagesReceived1"></textarea>
</template
Attempt 2: Binding Methods
Here Vue decides it doesn't like moustaches inside a textarea {{ messagesReceived2() }} and balks. It also doesn't allow messagesReceived2() or messagesReceived2 in v-model.
<script>
{
methods: {
messagesReceived2() {
return this.private.messagesReceived.join("\n");
},
addMessage(m) {
console.log("This is called multiple times, adding messages successfully");
this.private.messagesReceived.push(m);
}
}
</script>
<template>
<textarea rows="10" cols="40">{{ messagesReceived2() }}</textarea><!--Nope-->
<textarea rows="10" cols="40" v-model="messagesReceived2()"></textarea><!--Nope-->
<textarea rows="10" cols="40" v-model="messagesReceived2"></textarea><!--Nope-->
</template
You can define a data variable and set its value in the function. Then bind variable with textarea, not directly with the function.

How to prevent the data/method sharing of looped components in Vue.js

I have the vue component with $emit into component and let it return the data from the component. I will use the component to update current page's data. the codes below
Template:
<Testing
#update="update">
</Testing>
<AnotherComponent
:data="text"
>
</AnotherComponent>
Script:
method(){
update: function(data){
this.text = data.text
}
}
it work perfectly if only this one.
Now , i need to make a button to add one more component.
I use the for loop to perform this.
Template
<div v-for="index in this.list">
<Testing
:name="index"
#update="update">
</Testing>
<AnotherComponent
:data="text"
>
</AnotherComponent>
</div>
Script:
method(){
addList : function(){
this.list +=1;
},
deleteList : function(){
this.list -=1;
},
update: function(data){
this.text = data.text
}
}
The add and delete function run perfectly.
However , they share the "update" method and the "text" data.
so , If I change the second component , the first component will also changed.
I think this is not the good idea to copy the component.
Here are my requirements.
This component is the part of the form, so they should have different name for submit the form.
The another component" will use the data from the "testing component" to do something. the "testing" and "another component" should be grouped and the will not change any data of another group.
Any one can give me the suggestion how to improve these code? Thanks
What happends is that both are using the data form the parent, and updating that same data.
It seems that you are making some kind of custom inputs. In that case in your child component you can use 'value' prop, and 'input' event, and in the parent user v-model to keep track of that especific data data.
Child component BaseInput.vue:
<template>
<div>
<input type="text" :value="value" #keyup="inputChanged">
</div>
</template>
<script>
export default {
props: ['value'],
data () {
return {
}
},
methods: {
inputChanged (e) {
this.$emit('input', e.target.value)
}
}
}
</script>
And this is the code on the parent:
<template>
<div class="container">
<div class="row">
<div class="col-xs-12 col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3">
<base-input v-model="firstInputData"></base-input>
<p>{{ firstInputData }}</p>
<hr>
<base-input v-model="secondInputData"></base-input>
<p>{{ secondInputData }}</p>
</div>
</div>
</div>
<script>
import BaseInput from './BaseInput.vue'
export default {
components: {BaseInput},
data() {
return{
firstInputData: 'You can even prepopulate your custom inputs',
secondInputData: ''
}
}
}
</script>
In the parent you could really store the diferent models in an object as properties, and pass that object to that "The another component" , pass them as individual props... pass an array ....

Getting ref of component in async v-for

I have a list of items that don't get created until after an async call happens. I need to be able to get the getBoundingClientRect() of the first (or any) of the created items.
Take this code for instance:
<template>
<div v-if="loaded">
<div ref="myItems">
<div v-for="item in items">
<div>{{ item.name }}</div>
</div>
</div>
</div>
<div v-else>
Loading...
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
items: []
}
},
created() {
axios.get('/url/with/some/data.json').then((response) => {
this.items = response.data;
this.loaded = true;
}, (error) => {
console.log('unable to load items');
});
},
mounted() {
// $refs is empty here
console.log(this.$refs);
// this.$refs.myItems is undefined
}
};
</script>
So, I'm trying to access the myItems ref in the mounted() method, but the this.$refs is empty {} at this point. So, therefore, I tried using a component watch, and various other methods to determine when I can read the ref value, but have been unsuccessful.
Anyone able to lead me in the right direction?
As always, thanks again!!
UPDATES
Added a this.$watch in the mounted() method and the $refs still come back as {}. I then added the updated() method to the code, then was able to access $refs there and it seemed to work. But, I don't know if this is the correct solution?
How does vuejs normally handle something like dynamically moving a div to an on-screen position based on async data? This is similar to what I'm trying to do, grab an element on screen once it has been rendered first (if it even should be rendered at all based on the async data), then access it to do something with it (move it to a position)?
Instead of doing on this.$refs.myItems during mounted, you can do it after the axios promise returns the the response.
you also update items and loaded, sou if you want to use watch, you can use those
A little late, maybe it helps someone.
The problem is, you're using v-if, which means the element with ref="myItems" doesn't exist yet. In your code this only happens when Axios resolves i.e. this.loaded.
A better approach would be to use a v-show.
<template>
<div>
<div v-show="loaded">
<div ref="myItems">
<div v-if="loaded">
<div v-for="item in items">
<div>{{ item.name }}</div>
</div>
</div>
</div>
</div>
<div v-show="!loaded">
Loading...
</div>
</div>
</template>
The difference is that an element with v-show will always be rendered and remain in the DOM; v-show only toggles the display CSS property of the element.
https://v2.vuejs.org/v2/guide/conditional.html#v-show

Undefined props in Vue js child component. But only in its script

I have just started using Vue and experienced some unexpected behavior. On passing props from a parent to child component, I was able to access the prop in the child's template, but not the child's script. However, when I used the v-if directive in the parents template (master div), I was able to access the prop in both the child script and child template. I would be grateful for some explanation here, is there a better was of structuring this code? See below code. Thanks.
Parent Component:
<template>
<div v-if="message">
<p>
{{ message.body }}
</p>
<answers :message="message" ></answers>
</div>
</template>
<script>
import Answers from './Answers';
export default {
components: {
answers: Answers
},
data(){
return {
message:""
}
},
created() {
axios.get('/message/'+this.$route.params.id)
.then(response => this.message = response.data.message);
}
}
</script>
Child Component
<template>
<div class="">
<h1>{{ message.id }}</h1> // works in both cases
<ul>
<li v-for="answer in answers" :key="answer.id">
<span>{{ answer.body }}</span>
</li>
</ul>
</div>
</template>
<script>
export default{
props:['message'],
data(){
return {
answers:[]
}
},
created(){
axios.get('/answers/'+this.message.id) //only worls with v-if in parent template wrapper
.then(response => this.answers = response.data.answers);
}
}
</script>
this.message.id only works with v-if because sometimes message is not an object.
The call that you are making in your parent component that retrieves the message object is asynchronous. That means the call is not finished before your child component loads. So when your child component loads, message="". That is not an object with an id property. When message="" and you try to execute this.message.id you get an error because there is no id property of string.
You could continue to use v-if, which is probably best, or prevent the ajax call in your child component from executing when message is not an object while moving it to updated.