Update component when prop changes - vue.js

How do I force a component to re-render when a prop changes?
Say I have a component which holds:
<test-sub-component :layoutSetting="layoutSetting"></test-sub-component>
And I have my data object like so:
data() {
return {
layoutSetting: 'large'
}
},
Then with a click on a button i would like to change the settings of the prop passed, and the component should re-render, something like
<button #click="changeLayout">Change Layout</button>
And a method like
changeLayout() {
this.layoutSetting = 'small';
},
This changed the data object, but it doesnt re-render the component with the changed prop?

When you pass a property that is defined as camelCased in the component, you need to use the kebab-cased property name in the template.
<test-sub-component :layout-setting="layoutSetting"></test-sub-component>
console.clear()
Vue.component("test-sub-component", {
props: ["layoutSetting"],
template:`<div>{{layoutSetting}}</div>`
})
new Vue({
el: "#app",
data() {
return {
layoutSetting: 'large'
}
},
methods: {
changeLayout() {
this.layoutSetting = 'small';
},
}
})
<script src="https://unpkg.com/vue#2.2.6/dist/vue.js"></script>
<div id="app">
<test-sub-component :layout-setting="layoutSetting"></test-sub-component>
<button #click="changeLayout">Change Layout</button>
</div>

Related

How could I change an image in a child page when pressing a button in its parent page?

I have a DefaultLayout component with a dark mode toggle button which is its own component. One if its children (DefaultLayout's) is About.vue where I want a specific image to change its src depending on a localStorage value that can be set to either 'dark' or 'light'.
I've managed to read the localStorage value but the image does not change unless I refresh the page.
I'm new to Vue so I'm lost on how I can create a method to do this in DefaultLayout and change a variable in its child. I've tried to use an emit with no luck.
Could anyone point me in the right direction?
Yes, the local storage is for keeping data not propagate events.
The simplest way for you is to make a prop in child component and pass the value by this prop. But if you want to implement it as global variable the suggested way is by Pinia.
Below is a simple example
Vue.component('About', {
name: 'About',
template: `<div>
<div v-if="mode==='dark'">Dark</div>
<div v-else>Light</div>
</div>
`,
data() {
return {
mode: 'light',
};
},
mounted() {
this.setMode('white'); // In realtime use `this.getMode()` instead of 'white'
},
methods: {
setMode(val) {
this.mode = val;
},
getMode() {
return JSON.parse(localStorage.getItem('mode'));
}
}
});
var app = new Vue({
el: "#app",
template: `<div>
<input type="checkbox" v-model="toggler" #input="setVal" />
<About ref="about" />
</div>`,
data() {
return {
toggler: false,
};
},
methods: {
setVal() {
const mode = this.toggler === false ? 'dark' : 'light';
// localStorage.setItem('mode', mode); // In realtime uncomment this line
this.$refs.about.setMode(mode);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
</div>

Vue stored valued through props not being reactive

So I pass value using [props] and stored it in child component's data. However, when passing [props] value changes from parent, it's not updating in child component's data. Is there a fix for this..?
Here is the link to w3 test (I tried to clarify the problem as much as possible here)
<div id='app'>
<div id='parent'>
<button #click='current_value()'>Click to see parent value</button>
<br><br>
<button #click='change_value($event)'>{{ txt }}</button>
<br><br>
<child-comp :test-prop='passing_data'></child-comp>
</div>
<br><br>
<center><code>As you can see, this methods is <b>NOT</b> reactive!</code></center>
</div>
<script>
new Vue({
el: "#parent",
data: {
passing_data: 'Value',
txt: 'Click to change value'
},
methods: {
current_value(){
alert(this.passing_data);
},
change_value(e){
this.passing_data = 'New Vaule!!';
this.txt = 'Now click above button again to see new value';
e.target.style.backgroundColor = 'red';
e.target.style.color = 'white';
}
},
components: {
"child-comp": {
template: `
<button #click='test()'>Click here to see child (stored) value</button>
`,
props: ['test-prop'],
data(){
return {
stored_data: this.testProp
}
},
methods: {
test(){
alert(this.stored_data);
}
},
watch: {
stored_data(){
this.stored_data = this.testProp;
}
}
}
}
});
Props have one way data flow, that's why it doesn't react when you update it from the parent component. Define a clone of your prop at data to make it reactive, and then you can change the value within the child component.
Short answer: you don't need stored_data. Use alert(this.testProp) directly.
Long answer: when child component is created, stored_data get it's value from this.testProp. But data is local, it won't change automatically. That's why you need to watch testProp and set it again. But is not working because of a simple mistake, your watch should be:
watch: {
testProp(){ // here was the mistake
this.stored_data = this.testProp;
}
}

How can I access the value of a nested vue.js component?

I have a component called lbcontainer and a second component called lbitem.
Now I want to nest any number of lbitem components into one lbcontainer component.
The lbcontainer component has a method that should access all lbitem components that I have nested in the lbcontainer component.
Problem is: with ref I can get to the item via this.$ref.lbitem, but this only works in the component declaration, but not when I use the component later in HTML.
Vue.component('lbcontainer', {
methods: {
"showChildren": function() {
console.log(this.$refs);
}
},
template: `
<div>
<slot></slot>
<a #click='showChildren'>Show children</a>
</div>
`
});
Vue.component('lbitem', {
data: function() {
return {
value: ""
}
},
template: `
<input v-model="value"></span>
`
});
new Vue({
el: "#app",
data: {
},
methods: {
}
});
<div id="app">
<lbcontainer>
<lbitem ref="item"></lbitem>
<lbitem ref="item"></lbitem>
</lbcontainer>
</div>
When I press the button the console.log shows an empty object. How can I access the nested children?
Here is jsfiddle
Avoiding using $ref in vue ...
( $ref is populated after the first render...)
child can only communicate with their parent by event ...
or parent have all data and send to their child by props.
if parent and chidrend have to edit the data you can use v-model or .sync;
https://codesandbox.io/s/p95x1mxykm

Using Event Names stored in a variable in VueJs

In VueJS, a child component can emit an event, for example:
this.$emit('toggle-button')
In the parent, we can listen to this event as follows:
<my-component v-on:toggle-button="doSomething"></my-component>
This works great. But I have a requirement where the event name (in this case, toggle-button) is stored in a variable or Vuex store. So, I don't have the exact event name but a variable or store with the name of the event.
In such a case what would be the syntax for referring that that event name in the on-click directive?
For example, let say we have:
let eventName = 'toggle-button'
How can I use this variable (eventName) instead of the exact event name (toggle-button) in the following:
<my-component v-on:toggle-button="doSomething"></my-component>
You could use $on(EVENT_NAME, CALLBACK) in this case:
// <my-component ref="foo" />
this.$refs.foo.$on(eventName, doSomething)
Vue.component('my-component', {
template: `<button #click="$emit('click', $event)">Click</button>`
});
new Vue({
el: '#app',
mounted() {
const eventName = 'click';
this.$refs.foo.$on(eventName, this.doSomething);
},
methods: {
doSomething() {
alert('clicked');
}
}
})
<script src="https://unpkg.com/vue#2.5.17"></script>
<div id="app">
<my-component ref="foo" />
</div>
If you render your component using separate render function, this is easy:
export default {
props: {
eventName: {
type: String,
},
},
render: function (createElement) {
return createElement('my-component', {
on: {
[this.eventName]: (event) => {
console.log('Received event!')
}
}
})
}
}
Render functions also come with ability to dynamically specify a name for the component, if that is also required for your app

Vue.js 2.3 - Element UI Dialog - Sync and Props

I'm using vue-js 2.3 and element-ui. Since the update 2.3 of vue-js and the reintroduction of sync, things have changed and I have had a hard time figuring out my problem.
Here is the jsfiddle : https://jsfiddle.net/7o5z7dc1/
Problem
The dialog is only opened once. When I close it I have this error:
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:
"showDialog"
Questions
What am I doing wrong?
How can I fix the current situation?
EDIT
If I'm creating a data I manage to remove the error message but the dialog does not close anymore
<div id="app">
<left-panel v-on:show="showDialog = true">></left-panel>
<popup v-if="showDialog":show-dialog.sync="showDialog"></popup>
</div>
<template id="left-panel-template">
<button #click="$emit('show')">Show Component PopUp</button>
</template>
<template id="popup">
<el-dialog :visible.sync="showDialog" #visiblechange="updateShowDialog">I am the popup</el-dialog>
</template>
Vue.component('left-panel', {
template: '#left-panel-template',
methods: {
},
});
Vue.component('popup', {
template: '#popup',
props : ['showDialog'],
methods: {
updateShowDialog(isVisible) {
if (isVisible) return false;
this.$emit('update:showDialog', false )
},
},
});
var vm = new Vue({
el: '#app',
data: {
showDialog: false,
},
methods: {
}
});
You avoid the mutation warning by making a copy of the prop and mutating that instead of mutating the property directly.
Vue.component('popup', {
template: '#popup',
props : ['showDialog'],
data(){
return {
show: this.showDialog
}
},
methods: {
updateShowDialog(isVisible) {
if (isVisible) return false;
this.$emit('update:showDialog', false )
},
},
});
I also changed your template to handle the visible-change event correctly.
<el-dialog :visible.sync="show" #visible-change="updateShowDialog">I am the popup</el-dialog>
Updated fiddle.