public properties in vue.js showing warning - vue.js

Component Code
<template lang="html">
<div class="chat-users">
<ul class="list-unstyled">
<li v-for="chatuser in chatusers" class="left clearfix">
{{ chatuser.UserName }}
</li>
</ul>
</div>
</template>
<script>
export default {
props: ["chatusers"],
created() {
axios.post("some url").then(response => {
this.chatusers = response.data.Data;
});
}
}
</script>
<style>
</style>
Everything works perfectly, but I am getting below warning.
Avoid mutating a prop directly since the value will be overwritten
whenever the parent component re-renders. Instead, use a data or
computed property based on the prop's value. Prop being mutated:
"chatusers"

There is an explanation why prop should not be mutate in the vue documentation. If you need to mutate the state, perhaps you need a data instead.
Like:
export default {
data () { return { chatusers: null } },
created() {
axios.post("some url").then(response => {
this.chatusers = response.data.Data;
});
}
}

It is not the best way to mutate the props, since the parent is in control of this data and any changes it will overwrite child data, from the docs:
In addition, every time the parent component is updated, all props in
the child component will be refreshed with the latest value. This
means you should not attempt to mutate a prop inside a child
component. If you do, Vue will warn you in the console.

According to Vue.js data flow, the props received from parent should not be modified by child component. See the official documentation and this Stack Overflow answer.
As suggested by your warning: "use a data or computed property based on the prop's value", that in your case is the response received from axios call.

Related

Cannot read properties of undefined (reading '$refs') vue js

Getting the Error Cannot read properties of undefined (reading '$refs') though I'm having a reference in the template. Does it mean I must use Vue mounted hook ?
<div class="input-wrapper">
<input type="text" id="phone" placeholder="(555) 555-5555" ref="input"/>
</div>
<script>
this.$refs.input.addEventListener('input', function () {
// some code
});
</script>
Inside root of <script> of a Vue component, in both Vue 2 and Vue 3, this is undefined:
<script>
console.log(this) // undefined
</script>
See it here.
Vue template refs can only be accessed inside any hook or method happening after the component has been mounted and before it is unmounted.
Which means the earliest you can reference this.$refs is inside mounted. And the latest is in beforeUnmount. And you can also access them in any hook or method happening between those two moments.
Considering you're attempting to add an event listener to a HTMLInputElement, I'd recommend using the v-on directive, which automatically adds the event listener on mount and removes it on unmount.
In your case:
<template>
<div class="input-wrapper">
<input
type="text"
id="phone"
placeholder="(555) 555-5555"
#input="myFn" />
</div>
</template>
<script>
export default {
methods: {
myFn(event) {
console.log(event)
}
}
}
</script>
As a side note, you should know that a regular function's this doesn't have access to the component context, unless it's an arrow function:
export default {
mounted() {
this.$refs.input.addEventListener('input', function() {
/*
* Here `this` is the context of the current function, you can't
* access methods, computed, data or props of the component.
* You'd need to make it an arrow function to access the component scope
*/
})
}
}
Whereas in any method (e.g: the above myFn), this is the component context, which has access to all component members.

Calling function inside child component without an event?

Currently trying to use a method belonging to the parent
<p class="message-date text-center">
{{ $emit('format_date_day_month_year_time', message.date) }}
</p>
However I am getting the error.
Converting circular structure to JSON
--> starting at object with constructor 'Object'
How can I call a function inside a child component that does not rely on an event? I apologize for asking such a simple question but everything I was able to find on google is using $emit and using an event.
$emit was designed to only trigger an event on the current instance of vue. Therefore, it is not possible to receive data from another component this way.
For your case, I would suggest to use Mixins especially if you need to use certain functions among multiple vue components.
Alternately, let the child component call the the parent through $emit then receive the result from the parent through a prop.
Your code could be something as follows:
Child component
<template>
<p class="message-date text-center">
{{ date }}
</p>
</template>
<script>
export default {
name: 'Child',
props: {
date: String,
},
mounted() {
this.$emit("format-date", message.date);
},
}
</script>
Parent component
<template>
<Child :date="childDate" #format-date="formatChildDate" />
</template>
<script>
import Child from '#/components/Child';
export default {
components: {
Child,
},
data: () => ({
childDate: '',
}),
methods: {
formatChildDate(date) {
this.childDate = this.formatDateDayMonthYearTime(date)
},
formatDateDayMonthYearTime(date) {
//return the formatted date
},
},
}
</script>
with $emit you call a function where the Parent can listento.
where you are using it i would suggest a computed prop of the function.
But back to your Question here is a example of emiting and listen.
//Parent
<template>
<MyComponent #childFunction="doSomethingInParent($event)"/>
</template>
//Child
<template>
<button #click="emitStuff">
</template>
.....
methods:{
emitStuff(){
this.$emit(childFunction, somedata)
}
with the event you can give Data informations to a Parentcomponent.

component prop gets reevaluated on change of one Vuex state

I have two components with parent-child relations. The parent component can contain any number of child components using a v-for loop.
Essentially the child component contains a bootstrap table b-table and the parent component is just a wrapper around the child component. Therefore, the parent component can have any number of tables inside it.
The tables have the functionality to check or uncheck rows of the table. I want to keep track of all the selected rows of all the tables inside the parent component. I am using a Vuex store to keep the id of all the selected rows from all the tables. Each child component commits to the vuex store after any rows is checked/unchecked. It all works all fine till here. Using the vue devtools I can confirm that the vuex store always has the correct data in it.
(the vuex state property name is selectedObjects, it is an array of strings).
Now, I want to display some data on the parent component based of the vuex store value (using v-show). I want to show the data only when selectedObjects in the store is not an empty array.
I have a getter to get the selectedObjects state from the store, and I use this getter in the parent component as a computed property using mapGetters.
The problem is that everytime a child component makes any change to the vuex store, the parent component refreshes the prop that is passed on to the child components. The prop being passed is not dependant on the vuex store at all.
<template>
<div>
<div v-if="isDataLoaded">
<div>
<div>
<div>
<span v-show="showBulkActions"> Action1 </span>
<span v-show="showBulkActions"> Action2 </span>
<span v-show="showBulkActions"> Action3 </span>
<span v-show="showBulkActions">Action4</span>
<span> Action5 </span>
</div>
</div>
</div>
<div>
<template v-for="objectId in objectIds">
<ObjectList
:key="objectId"
:objects="getObjectsFor(objectId)"
:payload="payload"
></ObjectList>
</template>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
payload: Object,
isDataLoaded: false
},
data() {
return {
objectIds: [],
objects: []
};
},
computed: {
...mapGetters(["getSelectedObjects"]),
showBulkActions() {
// return true; --> works fine
//This does not work as expected
const selectedObjects = this.getSelectedObjects;
return selectedObjects.length > 0;
}
},
mounted: async function() {
// init this.objects here
},
methods: {
...mapGetters(["getObjects"]),
getObjectsFor(objectId) {
//this line gets executed everytime the selectedObjects is modified in vuex store.
//this method is only being called from one place (prop of ObjectList component)
return this.objects.filter(f => f.objectId === objectId);
}
}
};
</script>
According to my understanding, the getObjectsFor method should not be called when the selectedObjects array in vuex store is changed, because it does not depend on it.
Why is this happening ?
Your parent component template depends on selectedObjects Vuex store value (through showBulkActions computed prop and getSelectedObjects getter as computed prop).
Every time selectedObjects changes in store, update (and re-render) of parent component is triggered.
And as stated in Vue documentation:
every time the parent component is updated, all props in the child component will be refreshed with the latest value
This means expressions used to populate child components props (call to getObjectsFor method in your case) needs to be evaluated. That's why your method is called.
Solution would be to pass all objects to your child components as a prop and do the filtering (done in getObjectsFor method) inside your child component as computed prop...

What EXACTLY does mutating a child via emitted event .sync modifier do?

So I've been investigating how mutating child props from the parent can be done.
I already encountered the pitfall with using v-model.
It throws this error:
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "number1"
I learnt to deal with this problem by emitting events from the child and using .sync modifier in the parent.
However, I still feel like I didnt really understand whats going on under the hood in both cases.
As far as I understood, when mutating the childs properties from parent with v-model, the mutated data also mutates for any other parent importing the child.
To test this out, I built the following setup:
I have a child component with two inputs:
One using v-model, the other an emitted event to mutate the childs props.
We call it "compA":
<template>
<div>
<input
:value="number1"
#change="$emit('update:number1', $event.target.value)"
placeholder="number1emittedEvent"
/>
<input v-model="number1" placeholder="number1vmodel">
</div>
</template>
<script>
export default{
name: "compA",
props: {
number1: String
}
}
</script>
compA is being imported by compZ and compB. compZ also imports compB, and then compZ is finally being imported by myComplexView5.vue (my project uses routing).
compB:
<template>
<div>
<h1>compB containing compA</h1>
<compA/>
<p v-text="number1"></p>
</div>
</template>
<script>
import compA from "#/components/complexComponent5/compA.vue"
export default {
name: "compB",
components: {
compA
},
data(){
return {
number1:''
}
}
}
</script>
compZ:
<template>
<div>
<compA :number1.sync="number1"/>
<br>
<compB />
</div>
</template>
<script>
import compA from "#/components/complexComponent5/compA.vue"
import compB from "#/components/complexComponent5/compB.vue"
export default {
name: "compZ",
components: {
"compA" : compA,
"compB" : compB
},
data(){
return {
number1:''
}
}
}
</script>
myComplexView5.vue:
<template>
<div>
<h1>And More testing</h1>
<compZ />
</div>
</template>
<script>
import compZ from "#/components/complexComponent5/compZ.vue"
export default{
name: "myComplexView5",
components: {
compZ
}
}
</script>
Now, I expected the following behavior:
When inputting into the inputfield which has v-model, I'd expect the paragraph inside compB to display the changes, since its text value is bound to the number1 prop from child compA. Since I mutated the childs props directly, the changed value should show up in any of the childs parents (or grandparents and so on).
But it doesnt. And it gets even better: When I use the inputfield with the event emitting from child to parent, the paragraph inside compB receives the changes!
This is basically the opposite of what I've learned from the sync modifier docs:
In some cases, we may need “two-way binding” for a prop. Unfortunately, true two-way binding can create maintenance issues, because child components can mutate the parent without the source of that mutation being obvious in both the parent and the child.
That’s why instead, we recommend emitting events in the pattern of update:myPropName. For example, in a hypothetical component with a title prop, we could communicate the intent of assigning a new value with:
this.$emit('update:title', newTitle)
Then the parent can listen to that event and update a local data property, if it wants to
Maybe this is caused by the data() inside compB which sets number1 to EMPTYSTRING when the component rerenders? I don't know, I'm very new to vue and I don't really understand when components rerender. I also used this
data() {
return {
number1: ''
}
}
inside compB To prevent this error from occuring:
[Vue warn]: Property or method is not defined on the instance but referenced during render.
I don't really know how else to prevent this error from occuring, since it seems that even though compB imported the prop from compA, the prop still needs to be declared in compB Oo
EDIT:
I just found out that in the code I'm using, compZ still had this paragraph element:
<p v-text="number1"></p>
The string inputted into the inputfield appeared there, NOT in the paragraph element of compB. For some reason, even though I get no errors, neither through the emitted event from compA, nor through the v-model from compA does any change to the props inside compA show any impact inside compB... :(
I put your example code in a snippet at the bottom of the post so that we're on the same page about what code is actually being talked about (I removed the line with the <p v-text="number1"></p> from compB, because that component doesn't have a prop or data/computed property for number1).
Your compA component takes in a number1 prop, which it then uses for two inputs:
The first input uses the prop as its value and then emits an 'update:number1' event with the input's value in response to the input's change event. This allows you to specify the .sync modifier when the parent component binds a value to the compA component's number1 prop (as you do in compZ).
The second input directly binds the value of the number1 prop via v-model. This is not recommended (for reasons I'll explain), which is why you are seeing the "Avoid mutating a prop directly" warning. The effect of directly binding the number1 prop to this input is that whenever the value of this second input changes, the value of number1 will change, thus changing the value of the first input, and finally causing that first input to emit the update:number1 event. Technically Vue allows you to do this, but you can see how it can get confusing.
Your compB component simply renders some texts and a compA component without passing a value as a number1 prop to compA.
Your compZ component renders a compA component, binding its own number1 property value with the .sync modifier to the compA component. This compA component instance does not share any data with the compA component instance in the compB component, so we can't expect any changes to either component to affect the other.
Vue.component('compA', {
template: `
<div>
<input
:value="number1"
#change="$emit('update:number1', $event.target.value)"
placeholder="number1emittedEvent"
/>
<input v-model="number1" placeholder="number1vmodel">
</div>
`,
props: {
number1: String
}
})
Vue.component('compB', {
template: `
<div>
<h1>testString</h1>
<compA />
</div>
`
})
Vue.component('compZ', {
template: `
<div>
<compA :number1.sync="number1"/>
<br>
<compB />
</div>
`,
data() {
return {
number1: ''
}
}
})
new Vue({
el: '#app'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div>
<h1>And More testing</h1>
<comp-z/>
</div>
</div>

How to get child component's data in VueJs?

This is an easy question but I dont know the best way to do it correctly with Vue2:
Parent Component:
<template>
<div class="parent">
<child></child>
{{value}} //How to get it
</div>
</template>
Child Component:
<template>
<div class="child">
<p>This {{value}} is 123</p>
</div>
</template>
<script>
export default {
data: function () {
return { value: 123 }
}
}
</script>
Some ways you can achieve this include:
You can access the child VM directly via a ref, but this gets hairy because the ref won't be populated until the child component has mounted, and $refs isn't reactive, meaning you'd have to do something hacky like this:
<template>
<div class="parent">
<child ref="child"></child>
<template v-if="mounted">{{ $refs.child.value }}</template>
</div>
</template>
data() {
return {
mounted: false
}
},
mounted() {
this.mounted = true;
}
$emit the value from within the child component so that the parent can obtain the value when it is ready or whenever it changes.
Use Vuex (or similar principles) to share state across components.
Redesign the components so that the data you want is owned by the parent component and is passed down to the child via a prop binding.
(Advanced) Use portal-vue plugin when you want to have fragments of a component's template to exist elsewhere in the DOM.
Child components pass data back up the hierarchy by emitting events.
For example, in the parent, listen for an event and add the child value to a local one (an array for multiple child elements would be prudent), eg
<child #ready="childVal => { value.push(childVal) }"></child>
and in the child component, $emit the event on creation / mounting, eg
mounted () {
this.$emit('ready', this.value)
}
Demo ~ https://jsfiddle.net/p2jojsrn/