2

I am trying to create a Vue page that contains nested field collections. I.e. Parent form and repeatable child forms.

It works with the exception that when deleting a child form, the template renders incorrectly

Please see the fiddle example that I created - https://jsfiddle.net/c4marcus/1mu2oceb/8/

The sample data contains basic information for The Beatles. If you click the trash can next to "Ringo" then mistakenly "George" will disappear and not "Ringo".

However, when you click submit the correct data is being saved (see screenshot below).

I feel like the problem must lie with the MemberFormset vue component's remove method which is triggered by clicking the trash can button.

remove: function(index) {
    this.members.splice(index, 1)
    this.$emit('input', this.members)
},

Once spliced, the template should render the array of forms with the new data.

<div class="form-group" v-for="(member, index) in members">
    <member-form :model="member"
                 :index="index"
                 @input="update(index, $event)">

        <div slot="trash">
            <button type="button"
                    class="btn btn-default margin-top-md"
                    @click="remove(index)">

                <i class="fa fa-trash"></i>
            </button>
        </div>

    </member-form>

    <hr v-if="separator(index)" />
</div>

enter image description here

Ulysse BN
  • 10,116
  • 7
  • 54
  • 82
pymarco
  • 7,807
  • 4
  • 29
  • 40

1 Answers1

2

The main issue appears to be here:

 <member-form :model="member"
              :index="index"
              @input="update(index, $event)">

You need to provide a key for the custom component included in your loop. In this case you are not directly iterating on the custom component, but providing a key to Vue helps it determine it's strategy to update the DOM. To that end I added an id to each member object

members: [
  {name: 'John', id: 1},
  {name: 'Ringo', id: 2},
  {name: 'Paul', id: 3},
  {name: 'George', id: 4}
]

and updated the template to this:

 <member-form :model="member"
              :index="index"
              @input="update(index, $event)"
              :key="member.id">

One more thing, as pointed out in the comments, your add method needs to be updated to add a new id value.

add: function () {
  const newId = Math.max(this.members.map(m => m.id)) + 1
  this.members.push({name: null, id: newId})
},

Now your DOM is properly updated after a delete.

Here is the updated fiddle.

I noted a few things looking over some of the code that look like they fall into some Vue caveats. This code for example:

update: function(index, value) {
  this.members[index] = value
  this.$emit('input', this.members)
},

Looks like it would fall into Vue's array detection caveat. And althought it might not be causing issues right now, potentially might in the future.

tony19
  • 125,647
  • 18
  • 229
  • 307
Bert
  • 80,741
  • 17
  • 199
  • 164
  • It is not if you use the add member button and then delete again. Because when add is triggered no id is given (maybe `this.members.push({ id: generateId() }` would work) – Ulysse BN Aug 17 '17 at 18:34
  • @UlysseBN True, because a new id is not generated in his `add` method. I went ahead and updated that. There are a few other things that look like they call into weird areas, but I didn't try to fix *all* the code. – Bert Aug 17 '17 at 18:39
  • My approach for generating ID would have been to keep a [running index](https://stackoverflow.com/a/3231500/6320039) but this is ok too, +1 – Ulysse BN Aug 17 '17 at 18:43
  • Wow, thank you so much Bert! It hadn't crossed my mind to check the `v-for` docs, just wasn't expecting it. – pymarco Aug 17 '17 at 20:20
  • And thank you both for the additional help too! I'm trying to rapidly learn Vue and still have lots of detail to soak up. – pymarco Aug 17 '17 at 20:24