-1

In order to properly copy props to local data and manipulate them in your component you need to use Computed props.

What do you do when you want to set the default value of a computed prop to be based on a prop but to also be able to override its' value manually without resetting the entire computed property?

props: {
  thing: {
    type: Object,
    required: false,
    default: null,
  },
},
computed: {
  form() { 
      return { 
          name: this.thing.name,
          someLocalThing: 'George Harrington',
      }; 
  },
}

and then

<v-text-field v-model="form.name">
<v-text-field v-model="someLocalThing">

The problem is, changing someLocalThing, overrides/resets form.name (the computed prop is re-evaluated) so we lose the changes we had just previously done.

edit: this is an exact reproduction link : https://codesandbox.io/s/vuetify-2x-scope-issues-ct0hu

George Katsanos
  • 13,524
  • 16
  • 62
  • 98
  • And when `thing` prop will change (in a parent), you'll loose current value of `someLocalThing`. Why you merging them into single object like that ? Seems like very bad idea.... – Michal Levý Dec 11 '19 at 21:57
  • 1. When you say *'default value'* do you mean *'initial value'*? It sounds like you're describing one of the scenarios covered here: https://vuejs.org/v2/guide/components-props.html#One-Way-Data-Flow 2. *'... to properly copy props to local data ... you need to use Computed props.'* - I'm unclear what you mean by that but in general I don't think this is a good way to think about the relationship between props and computed properties. – skirtle Dec 11 '19 at 22:32
  • I think this clarifies my issue: https://codesandbox.io/s/vuetify-2x-scope-issues-ct0hu – George Katsanos Dec 12 '19 at 09:35
  • Totally wrong way to use `vue`. If you want to set `default` values use `life cycle hooks` and store those data in `data`. Then you can manipulate them as much as you need. – Daniyal Lukmanov Dec 12 '19 at 09:50
  • Storing in `data` has another issue: https://codesandbox.io/s/vuetify-2x-scope-issues-ct0hu – George Katsanos Dec 12 '19 at 09:53

3 Answers3

0

In order to properly copy props to local data and manipulate them

First, in your example you have no local data. What your computed property is is some temporary object, which can be recreated any time something changes. Don't do that.

If the prop is really just some initial value, you can introduce property in data() and initialize it right there from prop

If you are passing the prop to component in order to change its value, you probably want that changed value passed back to your parent. In that case you don't need to copy anything. Just props down, events up (either with v-model, .sync or just handling events manually)

In the case Object is passed by prop, you can also directly mutate object's properties - as long as you don't change prop itself (swap it for different object), everything is fine - Vue will not throw any warning and your parent state will be mutated directly

UPDATE

Now I have better understanding of use case. Question should be more like "Best way to pass data into a modal popup dialog for editing item in the list/table"

  1. Dialog is modal
  2. Dialog can be shown for specific item in a table by clicking button in it's row
  3. Dialog is cancelable (unless the user uses Save button, any changes made in the dialog should be discarded)

In this particular case I don't recommend using props to pass the data. Props are good for passing reactive values "down". Here you don't need or want dialog to react for data changes. And you also don't want use props for 1-time initialization. What you want is to copy data repeatedly at particular time (opening the dialog).

In component with table (row rendering):

<td>
   <v-btn rounded @click="openDialog(item)">open details</v-btn>
</td>

Place dialog component outside the table:

<person-form @update="onUpdate" ref="dialog"></person-form>

In methods:

openDialog(item) {
  this.$refs.dialog.openDialog(item);
},
onUpdate(item) {
  // replace old item in your data with new item
}

In dialog component:

openDialog(item) {
  this.form = { ...item };  // copy into data for editing
  // show dialog....
},
// Save button handler
onSave() {
  this.$emit("update", this.form);
  // hide dialog...
}

Demo

Justin Grant
  • 44,807
  • 15
  • 124
  • 208
Michal Levý
  • 33,064
  • 4
  • 68
  • 86
  • Hey Michal, I managed to reproduce my exact bug : https://codesandbox.io/s/vuetify-2x-scope-issues-ct0hu . this is why I resorted to Computed. local data didn't work. – George Katsanos Dec 12 '19 at 09:33
  • Yep, that's what happens when you use props as initial values for your `data()`. You can fix that by using computed property but not like an object - introduce one computed for each field. Prop should have a getter (value from prop) and setter (set new value to `data()`). – Michal Levý Dec 12 '19 at 10:23
  • And then you can make a step back and think about why you rendering new dialog instance into each row of your table. Doesn't seem like a good pattern to me.... – Michal Levý Dec 12 '19 at 10:27
  • I guess splitting the activation button and the actual dialog in two and moving the dialog outside the row would be the ultimate solution. I just wanted to keep the parent component smaller. – George Katsanos Dec 12 '19 at 10:41
  • As you are working with modal (only one can be open at a time), you can also make your life easier by just passing edited item into `openDialog` method, make a copy into `data()` and avoid all the hassle with props. [Here](https://codesandbox.io/s/vuetify-2x-scope-issues-p13rb) is how I would do that. Now its easy to split button from dialog....I'll leave that part for you – Michal Levý Dec 12 '19 at 10:58
  • thanks, I cant just copy the entire thing even though it looks easy because in my real scenario there's many properties that I need to manipulate or delete before I submit the form. But my question is this: how do you move the dialog out of the individual row / loop if you need to pass a prop that is an item of that loop/array of objects? – George Katsanos Dec 12 '19 at 13:09
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/204130/discussion-between-george-katsanos-and-michal-levy). – George Katsanos Dec 12 '19 at 13:22
  • did so, unfortunately what happens now is that the first item which a dialog is opened with, appears in all dialogs.. I use a local data variable to cache it. `openDialog(item)` .. and then `openDialog(item) { this.selectedItem = item; $refs['xx'].openDialog() }` and pass it as a prop : `` – George Katsanos Dec 14 '19 at 07:39
0

You could do something like this

props: {
  thing: {
    type: Object,
    required: false,
    default: null,
  },
},
data () {
    return {
        name: this.thing.name,
        someLocalThing: 'George Harrington'
    }
}

Now you can modify the data inside of component, and the props are still the same.

If you want to apply these changes directly to parent component as well, you will have to emit a function with the updated data.

Asim Khan
  • 1,969
  • 12
  • 21
0

I had the same issue today. In my use case, users are opening a dialog to edit an object, My solution was to fill the form on the button press to open the dialog. This seems to be a Vuetify issue. I have searched the Vuetify Github repo, but I have not been able to find this issue.

Regardless, here is my implementation (trimmed down for brevity).

@click="fillForm()" calls a function to fill the v-textarea s

 <v-dialog v-model="dialog" persistent max-width="600px">
        <template v-slot:activator="{ on }">
            <v-btn color="primary" v-on="on" @click="fillForm()" width="100%">Edit RFQ</v-btn>
        </template>
        ...
 </v-dialog>

script

export default {
        props: ['rfq'],
        data(){
            return {
                title: undefined,
                notes: undefined,
                companiesRequested: undefined,
            }
        },
        methods: {
            fillForm() {
                this.title = this.title ? this.title : this.rfq.title;
                this.notes = this.notes ? this.notes : this.rfq.notes;
                this.companiesRequested = this.companiesRequested ? this.companiesRequested : this.rfq.company_requested_ids;
            },
        }
}
AlphaKilo
  • 13
  • 4