Update property value of child component from parent component - vue.js

I have a parent component that has a button element. If user clicks that button, a dialog component should appear. The child component is the dialog component. However, the dialog won't open.
This is how I propagate props to my child component:
Parent component (template):
<v-btn
color="primary"
elevation="5"
#click="openCreateDialog = true"
small
>
<v-icon left>
mdi-plus
</v-icon>
Create
</v-btn>
<create-reservation-dialog :open="openCreateDialog" />
Child component (template):
<v-dialog v-model="openDialog" width="300">
<span>Opened</span>
</v-dialog>
Child component (JS):
export default Vue.extend({
name: "CreateReservationDialog",
props: {
open: {
type: Boolean,
required: true,
},
},
data() {
return {
openDialog: this.open,
}
},
});
Any ideas how can I fix this? Should I bind some method that will update openDialog variable in child? If so how?
Thank you in advance.

on clicking you are just updating the property value but it will not reload the dialog component. You can add v-if="openCreateDialog" in create-reservation-dialog to render it dynamically.
Live Demo (Please notice here, how mounted hook called in the child component) :
Vue.component('custom-dialog', {
props: ['open'],
template: '<p>{{ open }}</p>',
mounted() {
console.log('popup called');
}
});
var app = new Vue({
el: '#app',
data: {
openCreateDialog: false
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button #click="openCreateDialog = true">Open Dialog</button>
<custom-dialog v-if="openCreateDialog" :open="openCreateDialog">
</custom-dialog>
</div>

Related

How to call a component method each time this component show in vue2?

Vue component hasn't onShow lifecycle method.
How to call a component method each time this component show in vue2?
I have used this library for that purpose before:
https://github.com/Akryum/vue-observe-visibility
I am assuming you are dynamically hiding and showing the component and you want to trigger few functionality every time component show. If Yes, You can simply achieve this requirement by using v-if directive instead of v-show. As v-if will rebuild the component every time condition got passed.
Live Demo :
Vue.component('child', {
props: ['childmsg'],
template: '<p>{{ childmsg }}</p>',
mounted() {
console.log('child component mounted');
}
});
var app = new Vue({
el: '#app',
data: {
buttonMsg: 'Show Child Component',
showChild: false
},
methods: {
toggleBtn() {
this.showChild = !this.showChild;
this.buttonMsg = this.showChild ? 'Hide Child Component' : 'Show Child Component';
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button #click="toggleBtn">{{ buttonMsg }}</button>
<child v-if="showChild" childmsg="This is a child message"/>
</div>

Opening (and Closing) vue material dialog on different components

I'm trying to open a vue material dialog on a child component. I want the trigger button to remain on the parent and the dialog box template on a child component so that it would be easier to maintain.
The dialog box opens correctly, but when I click on the close button, the dialog box closes, but I'm unable to open it again with the trigger button. I'm using a prop to pass variables from the parent to the child.
How do I close the dialog box and be able to open it up again?
app.vue:
<template>
<div>
<button #click="showContextMenu = true">
<span>Show Context Menu</span>
</button>
<contextMenu :showContextMenu="showContextMenu"></contextMenu>
</div>
</template>
<script>
import contextMenu from "contextMenu.vue";
export default {
data() {
return {
showContextMenu: false,
};
},
components: {
contextMenu,
},
};
</script>
contextMenu.vue:
<template>
<md-dialog :md-active.sync="showContextMenu">
<md-dialog-title>Preferences</md-dialog-title>Dialog
<md-dialog-actions>
<md-button class="md-primary" #click="showContextMenu = false">Close</md-button>
</md-dialog-actions>
</md-dialog>
</template>
<script>
export default {
data() {
return {};
},
props: ["showContextMenu"],
};
</script>
Passing props to child components creates a one way binding, thus you cannot use them to pass state from child to its parent. You can have you contextMenu emit an event, on which you change the value of the showContextMenu variable in parent. Remember the following: props are used for passing state from parent to child components and events for doing the reverse.
The code would look like this:
app.vue:
<template>
<div>
<button #click="showContextMenu = true">
<span>Show Context Menu</span>
</button>
<contextMenu :showContextMenu="showContextMenu" #close="showContextMenu = false"></contextMenu>
</div>
</template>
<script>
import contextMenu from "contextMenu.vue";
export default {
data() {
return {
showContextMenu: false,
};
},
components: {
contextMenu,
},
};
</script>
contextMenu.vue:
<template>
<md-dialog :md-active.sync="showContextMenu">
<md-dialog-title>Preferences</md-dialog-title>Dialog
<md-dialog-actions>
<md-button class="md-primary" #click="$emit('close')">Close</md-button>
</md-dialog-actions>
</md-dialog>
</template>
<script>
export default {
data() {
return {};
},
props: ["showContextMenu"],
};
</script>
You are actually trying to set a property present in app, but you are toggling it false in contextMenu.
Mutating prop is never recommended.
Do this instead
App.vue
<template>
<div>
<button #click="showContextMenu = true">
<span>Show Context Menu</span>
</button>
<contextMenu v-if="showContextMenu"
:showContextMenu="showContextMenu"
#close-context="showContextMenu = false"> //capturing event close-context
</contextMenu>
</div>
</template>
Then in contextMenu, emit an event to parent.
contextMenu.vue
<template>
<md-dialog :md-active.sync="showContextMenu">
<md-dialog-title>Preferences</md-dialog-title>Dialog
<md-dialog-actions>
<md-button class="md-primary" #click="closeContext">Close</md-button>
</md-dialog-actions>
</md-dialog>
</template>
<script>
export default {
data() {
return {};
},
methods: {
closeContext() {
this.$emit('close-context'); // emitting to parent
},
props: ["showContextMenu"],
};
</script>

Vuetify: Navigation drawer: communicate v-model changes to parent co

I have created a Sidebar component using Vuetify's navigation drawer. The code looks something like this:
<template>
<v-navigation-drawer persistent clipped v-model="isVisible" fixed app>
<!-- content of the sidebar goes here -->
</v-navigation-drawer>
</template>
<script>
export default {
name: 'Sidebar',
props: {
visible: Boolean,
},
data() {
return {
isVisible: this.visible,
};
},
}
</script>
Please note that I am duplicating the visible prop with the isVisible data. I tried using the prop directly in the v-model but every time the sidebar closed, I would get a warning in the console about changing props directly, as they would be overwritten when the parent re-renders.
In the parent view, I have a button on the toolbar that is supposed to change icon depending on the visibility of the toolbar.
<template>
<v-container fluid>
<sidebar :visible="sidebarVisible"/>
<v-toolbar app :clipped-left="true">
<v-btn icon #click.stop="sidebarVisible = !sidebarVisible">
<v-icon v-html="sidebarVisible ? 'chevron_right' : 'chevron_left'"/>
</v-btn>
</v-toolbar>
<v-content>
<router-view/>
</v-content>
<v-footer :fixed="fixed" app>
<span>© 2017</span>
</v-footer>
</v-container>
</template>
<script>
import Sidebar from '#/components/Sidebar.vue';
export default {
name: 'MainView',
data() {
return {
sidebarVisible: false,
fixed: false,
title: 'Title',
};
},
components: {
Sidebar,
},
};
</script>
The problem I have is that if I close the sidebar by clicking outside of it, the icon of the button on the toolbar does not change to chevron-left. Moreover, in order to bring the sidebar back, I need to click on the button twice.
Clearly this is because the sidebarVisible data in the main view is not updated when the sidebar closes. How do I make sure that sidebarVisible is updated when the sidebar closes?
I am use next construction...
in my component
<template>
<v-navigation-drawer v-model="localDrawer"></v-navigation-drawer>
</template>
...
<script>
export default {
props: { value: { type: Boolean } },
data: () => ({
localDrawer: this.value
}),
watch: {
value: function() {
this.localDrawer = this.value
},
localDrawer: function() {
this.$emit('input', this.localDrawer)
}
}
}
</script>
in parent layer
<app-drawer v-model="drawer"></app-drawer>
it's work for me
Use v-bind:value or :value to bind the drawer value from props.
Child component:
<template>
<v-navigation-drawer v-bind:value="drawer"></v-navigation-drawer>
</template>
<script>
export default {
props : ['drawer']
}
</script>
Parent component:
<template>
<app-side-bar :drawer="drawer"/>
<v-app-bar app clipped-left>
<v-app-bar-nav-icon #click.stop="drawer = !drawer"></v-app-bar-nav-icon>
</v-app-bar>
</template>
Vuetify navigation drawer issue fix:
Reset your browser window to default 100%
Here is the code,
Template:
<nav>
<v-toolbar flat app>
<v-toolbar-side-icon class="grey--text" #click="toggle"></v-toolbar-side-icon>
<v-toolbar-title class="text-uppercase grey--text">
<span class="font-weight-light">Repo</span>
<span>hub</span>
</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn flat color="grey">
<span>Sign Out</span>
<v-icon right>exit_to_app</v-icon>
</v-btn>
</v-toolbar>
<v-navigation-drawer app v-model="drawer" class="indigo">
<p>test</p>
</v-navigation-drawer>
</nav>
Script:
export default {
data() {
return {
drawer: false
};
},
methods:{
toggle(){
this.drawer = !this.drawer;
}
}
};

Vue closing component returns avoid mutating a prop directly

I have an component that I want to use on different pages. Well, it is working well till the first toggle. It shows like it used to, but when I click the 'Close' button, it closes, but console outputs :
[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: "visible"
found in
---> at src/components/Snackbar.vue
at src/views/Login.vue
And after that it doesn't show up on click
Any way to fix this?
Snackbar.vue
<template>
<v-snackbar v-model.sync="visible" :timeout="5000" bottom>
{{ content }}
<v-btn flat color="primary" #click.native="visible = false">Close</v-btn>
</v-snackbar>
</template>
<script>
export default {
name: 'snackbar',
props: [
'visible',
'content'
]
}
</script>
Login.vue
<template>
<div class="login">
<Snackbar :visible="snackbar.visible" :content="snackbar.content"></Snackbar>
</div>
</template>
<script>
import Snackbar from '#/components/Snackbar.vue'
export default {
components: {
Snackbar
},
data: function() {
return {
email: '',
password: '',
snackbar: {
visible: false,
content: ''
}
}
},
methods: {
login: function() {
if (this.email != '' && this.password != '') {
// Do something
} else {
this.snackbar.content = 'Fields can\'t be empty';
this.snackbar.visible = true;
}
}
}
}
</script>
The console error is being triggered by this:
#click.native="visible = false"
The component is directly mutating the incoming prop. If you want to keep this level of control where the parent component controls the visibility you'll have to do it by having the click event emit an event, which the parent component receives and sets this.snackbar.visible = false thereby triggering a prop change and the child component is hidden.
<Snackbar :visible="snackbar.visible" :content="snackbar.content"
v-on:requestClose="close"></Snackbar>
<v-btn flat color="primary" #click.native="$emit('requestClose')">Close</v-btn>
methods: {
close: function() {
this.snackbar.visible = false;
}
}

vue reload child component

I'm using vue, version 2.5.8
I want to reload child component's, or reload parent and then force children components to reload.
I was trying to use this.$forceUpdate() but this is not working.
Do You have any idea how to do this?
Use a :key for the component and reset the key.
See https://michaelnthiessen.com/force-re-render/
Add key to child component, then update the key in parent. Child component will be re-created.
<childComponent :key="childKey"/>
If the children are dynamically created by a v-for or something, you could clear the array and re-assign it, and the children would all be re-created.
To simply have existing components respond to a signal, you want to pass an event bus as a prop, then emit an event to which they will respond. The normal direction of events is up, but it is sometimes appropriate to have them go down.
new Vue({
el: '#app',
data: {
bus: new Vue()
},
components: {
child: {
template: '#child-template',
props: ['bus'],
data() {
return {
value: 0
};
},
methods: {
increment() {
this.value += 1;
},
reset() {
this.value = 0;
}
},
created() {
this.bus.$on('reset', this.reset);
}
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
<child :bus="bus">
</child>
<child :bus="bus">
</child>
<child :bus="bus">
</child>
<button #click="() => bus.$emit('reset')">Reset</button>
</div>
<template id="child-template">
<div>
{{value}} <button #click="increment">More!</button>
</div>
</template>
I'm using directive v-if which is responsible for conditional rendering. It only affects reloading HTML <template> part. Sections created(), computed are not reloaded. As I understand after framework load components reloading it is not possible. We can only re render a <template>.
Rerender example.
I have a Child.vue component code:
<template>
<div v-if="show">
Child content to render
{{ callDuringReRender() }}
</div>
</template>
<script>
export default {
data() {
return {
show: true
}
}
,
methods: {
showComponent() {
this.show = true
},
hideComponent() {
this.show = false
},
callDuringReRender() {
console.log("function recall during rendering")
}
}
}
</script>
In my Parent.vue component I can call child methods and using it's v-if to force the child rerender
<template>
<div>
<input type="button"
value="ReRender the child"
#click="reRenderChildComponent"/>
<child ref="childComponent"></child>
</div>
</template>
<script>
import Child from './Child.vue'
export default {
methods: {
reRenderChildComponent(){
this.$refs.childComponent.hideComponent();
this.$refs.childComponent.showComponent()
}
},
components: {
Child
}
}
</script>
After clicking a button in console You will notice message "function recall during rendering" informing You that component was rerendered.
This example is from the link that #sureshvv shared
import Vue from 'vue';
Vue.forceUpdate();
// Using the component instance
export default {
methods: {
methodThatForcesUpdate() {
// ...
this.$forceUpdate(); // Notice we have to use a $ here
// ...
}
}
}
I've found that when you want the child component to refresh you either need the passed property to be output in the template somewhere or be accessed by a computed property.
<!-- ParentComponent -->
<template>
<child-component :user="userFromParent"></child-component>
</template>
<!-- ChildComponent -->
<template>
<!-- 'updated' fires if property 'user' is used in the child template -->
<p>user: {{ user.name }}
</template>
<script>
export default {
props: {'user'},
data() { return {}; }
computed: {
// Or use a computed property if only accessing it in the script
getUser() {
return this.user;
}
}
}
</script>