Attempting to pass props through render in Vue.js and watch them - javascript

I'm having trouble passing a prop from a parent down through a created child using CreateElement/render in vue.js and then watching it.
Here is my parent component
Vue.component('my-drawing', MyDrawing)
new Vue({
el: '#drawing',
mounted() {
Bus.$on('emitColorSelection', (emitString) => {
console.log("inside socket.js/my-drawing and emitString is ", emitString);
this.useColor = emitString;
console.log('inside socket.js/my-drawing and this.useColor after set is ', this.useColor);
})
},
data() {
return {
channel2: null,
canvases: [],
useColor: 'rgba(255, 0, 0, 1)'
}
},
render(createElement) {
return createElement(MyDrawing, {
props: {
useThisColor: this.useColor
}
})
}
});
So you can see here is that I take the value of the emit for some bus and then I pass that to useColor. I would like to then pass this value to my render function as useThisColor.
Here then is the child.
<template>
//my template stuff
</template>
<script>
//stuff
watch: {
useThisColor (n, o) {
console.log("useThisColor watch, ", n, o) // n is the new value, o is the old value.
}
}
//stuff continues
So this watch tag doesn't output. I've also tried putting the props in the template to no effect, as well as trying to output it on a Updated: tag. I've also attempted to set props in the parent using quotes. Nothing so far has worked and I am a little confused. If anyone has any ideas please let me know.

I expect the issue here is you simply didn't define the property, useThisColor, on the MyDrawing component.
Here is an example.

Related

vuejs component array push watcher

confusing title, I wasn't sure how else to write it.
So i have a component and it accepts a prop which is an array.
What i am looking to do, is on array.push() i would like a function to trigger for that array element.
so it is an alert toast message, and i want it to stay up for 3 seconds. so i was thinking about something like
watch: {
arrayObj: function () {
let self = this
setTimeout(function() {
self.dismiss(this.id)
}, 3000)
}
}
but i think i may be missing something here. How do i get the latest pushed object's reference so i make sure the dismiss method calls the correct alert? Is watch even the correct way to go about this?
I'm not entirely clear on what behavior you're trying to get, but it seems to me that you need to keep track of the latest pushed item and pass that to your component. The component would watch latestPushed and act accordingly.
If the latest pushed item could be repeated, you would not see a change when a duplicate value is pushed. In that case, it makes sense to pass an event bus prop to the component and send and event when the parent pushes.
new Vue({
el: '#app',
data: {
bus: new Vue(),
theArray: [1]
},
methods: {
push() {
const value = Math.floor(Math.random() * 10);
this.theArray.push(value);
this.bus.$emit('push', value);
}
},
components: {
toasterNote: {
props: ['bus'],
data() {
return {
active: false,
latestPush: null,
timer: null
};
},
created() {
this.bus.$on('push', (newValue) => {
this.latestPush = newValue;
this.active = true;
clearTimeout(this.timer);
this.timer = setTimeout(() => this.active = false, 3000);
});
}
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="app">
<div>{{theArray}}</div>
<button #click="push">Push</button>
<toaster-note inline-template :bus="bus">
<div v-if="active">
You pushed {{latestPush}}
</div>
</toaster-note>
</div>

Updating data from Vuex get a infinite loop in watcher

I have a "dumb" component that just get props from a parent. The user can change a selector which fires an action (using Vuex) to get new data. When this new data has been received I want to pass it to the child and re-render that component with the new data. Unfortutalely I keep getting this warning in my watcher. Please help :slight_smile:
export default {
name: 'bubbles',
props: {
awesomeData: {
type: Array,
required: true
}
},
data () {
return {
title: 'Best component ever'
}
},
watch: {
awesomeData (newData) {
console.log('hello world')
this.refreshSomethingAwesome(newData)
}
},
methods: {
refreshSomethingAwesome (newData) {}
}
}
s
101 hello world
[Vue warn]: You may have an infinite update loop in watcher with expression "awesomeData"
I'm trying a solution pretty much exactly the same as this: Vuex Examples
But can't seem to get it working... hmmm
Found out the reason I was getting the loop was because I was actually trying to sort mutable data from the property (thought it was immutable)
let options = {
children: newData.sort((a, b) => a.value - b.value)
}
I changed it to something like:
const sortedNewData = [...newData].sort((a, b) => a.value - b.value)
let options = {
children: sortedNewData
}
Note: To prevent this across my app, I might wrap my initial state in a Map from immutable.js
import { Map, fromJS } from 'immutable'
const initialState = Map({
awesomeData: fromJS([])
})
Helpful article: alligator.io

operating on props in vue.js components

Im pretty newm to vue and i'm trying to migrate the frontend of my laravel project to vue but i'm having an issue with it, i'm trying to loop through an array of provided objects called rooms and create divs for each of the in my component as well as setting the default room_id as the first id of the room. The problem is when is access the provided prop array called 'room' in the dom (html) it works flawlessly, but in my vue code for the component file it always seems to be undefined or empty
Here is my components vue code:
export default {
created() {
//this.loadMessages(this.room_id)
console.log(this.first_room) //undefined;
console.log(this.rooms) //empty array;
},
props: ['rooms','first_room'],
computes:{
myrooms: function(){
return this.first_room;
}
},
data()
{
return{
messages: [],
newMessage: '',
room_id: 1 //for test purposes, this works,
}
},
methods: {
loadMessages(id)
{
axios.get('/messages/'+id).then(response => {
this.messages = response.data;
console.log(response.data);
});
}
}
}
the important part of the component html
<div v-for="room in rooms">
<div class="chat-user room">
<div v-for="other in room.others">
<img class="chat-avatar img-circle" :src="other.image" alt="image" >
<div class="chat-user-name">
<a :href="'/user/' + other.id">{{ other.name}}</a>
</div>
</div>
</div>
</div>
//this all works, just for reference
method where i set the values passed to the prop in my main vue instance
EDIT: THE PARENT INSTANCE CODE
Oh and i cant seem too access the rooms array being passed as it is always empty IN code but it loops in the html
window.App.app= new Vue({
el: '#wrapper',
data: {
messages: [],
rooms: [],
test: 'yes',
first: ''
},
created() {
this.fetchRooms();
this.first = 1;
},
methods: {
fetchMessages(id) {
console.log(id);
},
fetchRooms()
{
axios.get('/rooms').then(response => {
this.rooms = response.data;
});
}
}
});
finally where i call my component
<chat-messages :rooms="rooms" :first_room="1"></chat-messages>
//variables referenced from main vue instance
I have literally torn most of my hair on this, please any help is appreciated
In the child component to which the props are passed on.
export default {
created() {
console.log(this.first_room) //undefined;
},
props: ['rooms','first_room'],
computed :{
myrooms: function(){
return this.first_room;
}
},
data () {
return {
messages: [],
newMessage: '',
room_id: 1 //for test purposes, this works,
}
},
watch: {
rooms (n, o) {
console.log(n, o) // n is the new value, o is the old value.
}
},
methods: {
loadMessages (id) {
axios.get('/messages/'+id).then(response => {
this.messages = response.data;
console.log(response.data);
});
}
}
}
You can add a watch on data properties or computed to see the change in their values.
In the question, (as what it appears to be the case), you have consoled the value of the props in the created lifecycle, the props' value gets changed by an API call in the parent component, after the creation of the child component. That explains why your template shows the data but not in the console in the created lifecycle hook.

Unable to access props values in data method in vuejs

I have the code (vuejs2) -
Vue.component('competetion-list', {
template: `<div>{{totalCompetetions}}</div>`,
props: ['values'],
data: function () {
return { totalCompetetions: this.values.length}
}
})
Nothing is printed on the page but if I change the template value to
template: `<div>{{this.values.length}}</div>`
it prints 15. What am I doing wrong and how can I pass the props to the data?
Any help is much appreciated.
I was unable to assign the prop values to data totalCompetetions in the following way -
data: function () {
return { totalCompetetions: this.values.length}
}
But I was able to do it using the watch, computed, and methods properties.
With watch property -
watch: {
values: function(){
this.totalCompetitions = this.values;
}
}
With computed property -
computed:{
competition:{
get: function(){
return this.values.length;
}
}
With methods property -
methods:{
competitionn: function(){
return this.values.length;
}
}
But for computed and methods properties, I needed to set totalCompetetions in the following way -
For computed -
template: `<div><p>{{totalCompetitions = competition}}</p></div>` //without parenthesis
For methods -
template: `<div><p>{{totalCompetitions = competition()}}</p></div>` //with parenthesis
You code does work.
I guess the problem is your parent component. Did you pass the values correctly? for example:
<competetion-list :values="[1, 2, 3]"></competetion-list>
Besides, for your case I'd say computed properties is a better solution.
computed: {
totalCompetetions () {
return this.values.length
}
}
From the data() method, you should be able to reference the component's properties using this.
Try following:
Vue.component('competetion-list', {
template: `<div>{{totalCompetetions}}</div>`,
props: ['values'],
data: function () {
var data = { totalCompetetions: this.values.length}
return data
}
})
As validly mentioned in the comment, if values array is changing later, you may have to put a watcher on the prop and inside watcher, set totalCompetetions as this.values.length.

What's the correct way to pass props as initial data in Vue.js 2?

So I want to pass props to an Vue component, but I expect these props to change in future from inside that component e.g. when I update that Vue component from inside using AJAX. So they are only for initialization of component.
My cars-list Vue component element where I pass props with initial properties to single-car:
// cars-list.vue
<script>
export default {
data: function() {
return {
cars: [
{
color: 'red',
maxSpeed: 200,
},
{
color: 'blue',
maxSpeed: 195,
},
]
}
},
}
</script>
<template>
<div>
<template v-for="car in cars">
<single-car :initial-properties="car"></single-car>
</template>
</div>
</template>
The way I do it right now it that inside my single-car component I'm assigning this.initialProperties to my this.data.properties on created() initialization hook. And it works and is reactive.
// single-car.vue
<script>
export default {
data: function() {
return {
properties: {},
}
},
created: function(){
this.data.properties = this.initialProperties;
},
}
</script>
<template>
<div>Car is in {{properties.color}} and has a max speed of {{properties.maxSpeed}}</div>
</template>
But my problem with that is that I don't know if that's a correct way to do it? Won't it cause me some troubles along the road? Or is there a better way to do it?
Thanks to this https://github.com/vuejs/vuejs.org/pull/567 I know the answer now.
Method 1
Pass initial prop directly to the data. Like the example in updated docs:
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
But have in mind if the passed prop is an object or array that is used in the parent component state any modification to that prop will result in the change in that parent component state.
Warning: this method is not recommended. It will make your components unpredictable. If you need to set parent data from child components either use state management like Vuex or use "v-model". https://v2.vuejs.org/v2/guide/components.html#Using-v-model-on-Components
Method 2
If your initial prop is an object or array and if you don't want changes in children state propagate to parent state then just use e.g. Vue.util.extend [1] to make a copy of the props instead pointing it directly to children data, like this:
props: ['initialCounter'],
data: function () {
return {
counter: Vue.util.extend({}, this.initialCounter)
}
}
Method 3
You can also use spread operator to clone the props. More details in the Igor answer: https://stackoverflow.com/a/51911118/3143704
But have in mind that spread operators are not supported in older browsers and for better compatibility you'll need to transpile the code e.g. using babel.
Footnotes
[1] Have in mind this is an internal Vue utility and it may change with new versions. You might want to use other methods to copy that prop, see How do I correctly clone a JavaScript object?.
My fiddle where I was testing it:
https://jsfiddle.net/sm4kx7p9/3/
In companion to #dominik-serafin's answer:
In case you are passing an object, you can easily clone it using spread operator(ES6 Syntax):
props: {
record: {
type: Object,
required: true
}
},
data () { // opt. 1
return {
recordLocal: {...this.record}
}
},
computed: { // opt. 2
recordLocal () {
return {...this.record}
}
},
But the most important is to remember to use opt. 2 in case you are passing a computed value, or more than that an asynchronous value. Otherwise the local value will not update.
Demo:
Vue.component('card', {
template: '#app2',
props: {
test1: null,
test2: null
},
data () { // opt. 1
return {
test1AsData: {...this.test1}
}
},
computed: { // opt. 2
test2AsComputed () {
return {...this.test2}
}
}
})
new Vue({
el: "#app1",
data () {
return {
test1: {1: 'will not update'},
test2: {2: 'will update after 1 second'}
}
},
mounted () {
setTimeout(() => {
this.test1 = {1: 'updated!'}
this.test2 = {2: 'updated!'}
}, 1000)
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div id="app1">
<card :test1="test1" :test2="test2"></card>
</div>
<template id="app2">
<div>
test1 as data: {{test1AsData}}
<hr />
test2 as computed: {{test2AsComputed}}
</div>
</template>
https://jsfiddle.net/nomikos3/eywraw8t/281070/
I believe you are doing it right because it is what's stated in the docs.
Define a local data property that uses the prop’s initial value as its initial value
https://vuejs.org/guide/components.html#One-Way-Data-Flow
Second or third time I run into that problem coming back to an old vue project.
Not sure why it is so complicated in vue, but it can we done via watch:
export default {
props: ["username"],
data () {
return {
usernameForLabel: "",
}
},
watch: {
username: {
immediate: true,
handler (newVal, oldVal) {
this.usernameForLabel = newVal;
}
},
},
Just as another approach, I did it through watchers in the child component.
This way is useful, specially when you're passing an asynchronous value, and in your child component you want to bind the passed value to v-model.
Also, to make it reactive, I emit the local value to the parent in another watcher.
Example:
data() {
return {
properties: {},
};
},
props: {
initial-properties: {
type: Object,
default: {},
},
},
watch: {
initial-properties: function(newVal) {
this.properties = {...newVal};
},
properties: function(newVal) {
this.$emit('propertiesUpdated', newVal);
},
},
This way I have more control and also less unexpected behaviour. For example, when props that passed by the parent is asynchronous, it may not be available at the time of created or mounted lifecycle. So you can use computed property as #Igor-Parra mentioned, or watch the prop and then emit it.
Following up on Cindy's comment on another answer:
Be carful. The spread operator only shallow clones, so for objects
that contain objects or arrays you will still copy pointers instead of
getting a new copy.
Indeed this is the case. Changes within objects inside arrays will still propagate to your components even when a spread operator is employed.
Here was my solution (using Composition API):
setup() {
properties = ref([])
onMounted(() => {
properties.value = props.initialProperties.map((obj) => ({ ...obj }));
})
}
This worked to set the values and prevent them from getting changed, even if the data was changed in the parent component.

Categories

Resources