2
votes

In Vue/Vuetify, how do we hide/show dialogs from parent? I'm trying to use v-model and here is a simplified version of my setup:

Parent component (just a button that triggers the child component to show)

<template>
<div>
    <v-btn class="ma-2" outlined fab color="red" small @click.stop="editItem()">
        <v-icon size="16">mdi-close-circle</v-icon>
    </v-btn>
    <user-dialog v-model="dialog" :eitem="editedItem" class="elevation-2" />
</div>
</template>

<script>
import UserDialog from "./UserDialog.vue";
export default {
    components:{
        UserDialog
    },
    data() {
        return {
            counter: 0,
            dialog: false,
            editedItem: {},
        }
    },
    methods: {
        editItem: function() {            
            this.counter++;       
            this.editedItem = Object.assign({}, {
                title: 'some title' + this.counter,
                details: 'some details for this item'
            });        

            this.dialog = true;
        },
    },
}
</script>

Child component (basically a dialog box)

<template>
    <v-dialog v-model="value" max-width="500px">
        <v-card>
            <v-card-title>
                <span class="headline">A Dialog</span>
            </v-card-title>

            <v-card-text>
                <v-container grid-list-md>
                    <v-layout wrap>
                        <v-flex xs12>
                            <v-text-field v-model="eitem.title" label="Title"></v-text-field>
                        </v-flex>
                        <v-flex xs12>
                            <v-text-field v-model="eitem.details" label="Details"></v-text-field>
                        </v-flex>
                    </v-layout>
                </v-container>
            </v-card-text>

            <v-card-actions>
                <v-spacer></v-spacer>
                <v-btn color="blue darken-1" text @click.stop="save">Save</v-btn>
                <v-btn color="blue darken-1" text @click.stop="close">Cancel</v-btn>
            </v-card-actions> 
        </v-card>
    </v-dialog>
</template>

<script>
    export default {
        props: {
            value: Boolean, 
            eitem: Object,
        },
        data() {
            return {
                editedItem: this.eitem,
            }
        },
        methods: {
            save() {
                //perform save
                this.$emit('input', false);
            },
            close() {
                this.$emit('input', false);
            },
        },
    }
</script>

This setup works, but give the following 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: "value"

But if act upon this advice and declare a data item in the child component and set v-model of the v-dialog to this data item, the dialog stops showing up upon click.

I perhaps understand why it does that, but cannot figure out a proper way of fixing this that doesn't show warnings. Can anyone help me with this?

1
Instead of using v-model bind the v-text-field's value to the prop. And on input emit a custom event to the parent and let it update the prop's value. You can use a computed property with a getter/setter.Husam Ibrahim
@HusamIbrahim: I hear that v-model is a syntactic sugar that does exactly the thing that you described. How'd that approach be different?dotNET
Just to clarify, those text-field bindings are not the issue at hand right now. I'm actually struggling with the v-dialog's v-model at the moment. The gist of the problem in my understanding is that I need a way to bind a local data item with a prop, so that whenever the parent component changes its bound data item, the child component is notified about this change and in response the child component updates its local data item (which will then trigger v-dialog to appear or disappear..dotNET
The difference is that v-model will attempt to directly modify the value on input and that's the equivalent of @input="value=$event.target.value". What you want is to emit a custom event to the parent via @input="()=>{$emit('updateValue', newValue}" and have the parent listen for an updateValue event to update the value of the prop. That will automatically be reflected in the child component after update.Husam Ibrahim
I see that you declare value as a prop to be used with v-dialog but you don't pass it's value from the parent. In that case you should specify a default value. And instead of using v-model on the value directly use a computed property with a getter/setter. That way if a value is passed from the parent you can emit a custom event to the parent to update the prop's value. Otherwise you can update a local value instead.Husam Ibrahim

1 Answers

2
votes

Since Vue throws a warning when you mutate props, you should not use v-model with props. To handle this use the following pattern:

computed: {
  propModel: {
    get () { return this.value },
    set (value) { this.$emit('input', value) },
  },
},

Define computed property with getter, that returns props.value, and setter that emits input event (that will be successfully handled in parent, since you use v-model)

Don't forget to chage your template: <v-dialog v-model="propModel" max-width="500px">