1

I want to pass an Array of Objects from a parent to child component. The child component will modify this array (but not perform a DML). This is what i tried

somewhere in parent.js

@track tempList = [];
connectedCallback(){
        getCourseModules({courseId:this.recordId})
        .then(result =>{
            this.moduleList = result;
            this.moduleList.forEach(item=>{
                var a = {};
                a.id = item.LM__c;
                a.navigate = item.LM_c;
                a.label = item.ModName__c;
                a.order = item.Order__c;
                this.tempList.push(a);
            });
        })
        .catch(error => {
            console.error(error);
        })
    }

Somewhere in parent.html

<template>
    <c-child title="Modules" item-list={tempList} ></c-child>
</template>

Somewhere in child.js

    @api
    set itemList(value){
        this.privateItemList = value;
      //shallow copy the array
        this.list = [...value];
    }
    get itemList(){
        return this.privateItemList;
    }

Somewhere in child.html

<template iterator:item={list}>
  <tr key={item.value.id}>
  <td>{item.value.order}</td>
  <td><lightning-button label={item.value.label} data-index={item.value.navigate} variant="base" onclick={linkClick}></lightning-button></td>
</template>

I don't see anything rendering in the child component.

Workaround-

//shallow copy the array in parent.js like this
 this.moduleList = result;
 this.moduleList.forEach(item=>{
      var a = {};
      a.id = item.LM__c;
      a.navigate = item.LM_c;
      a.label = item.ModName__c;
      a.order = item.Order__c;
      this.tempList.push(a);
 });
 this.tempList = JSON.parse(JSON.stringify(this.tempList));

//shallow copy the array in child.js like this

    @api
    set itemList(value){
        this.privateItemList = value;
      //shallow copy the array
        this.list = JSON.parse(JSON.stringify(value));
    }

This is working. I wanted to understand-

  1. If this approach of JSON.parse + JSON.stringify might break with future releases of LWC (is it stable enough ?)
  2. Is there a more stable way to passing arrays (that need to be mutated by the child)
  3. Will this approach cause performance issues ? I hope not, because i'm creating a copy of my original Array and not a reference.

1 Answer 1

4

If this approach of JSON.parse + JSON.stringify might break with future releases of LWC (is it stable enough ?)

Yes, it's perfectly normal JS, although not the most efficient. However, a point to note, this is a deep copy, not a shallow copy.

Is there a more stable way to passing arrays (that need to be mutated by the child)

The array is wrapped behind a read-only ObservableMembrane, so you have to make a copy. A simpler way to write this could be:

this.privateItemList = value.map(item => {...item});

This copies the array elements and duplicates the items within (one level only), so a shallow copy of the indices. This should work for most trivial cases, just be aware that nested objects would still be read-only.

There's no need to copy in the parent, because even then, the parent would be passing a read-only copy to the child (this happens at the moment the object is passed in via attribute, after you've already made the copy).

Will this approach cause performance issues ? I hope not, because i'm creating a copy of my original Array and not a reference.

There is a performance cost for a copy, but this is usually minor, even with tens of thousands of entries. JS is very efficient at what it does.


But what about an alternative?

The usual method for this type of update is to send an event to the parent to notify it of changes. The parent can then make the changes in its variable, and those changes propagate back to the child. This allows you to still pass by reference, which avoids all the copying and GC (garbage collection) associated with making copies for every change. Consider designing a system where the child notifies the parent, and the parent makes the relevant changes.


<!-- parent.html -->
<c-child onchange={handleChange} ...>

// parent.js
handleChange(event) {
  Object.assign(this.tempList[event.detail.index], event.detail.changes);
}

// child.js
notifyParentOfChange(event) {
  let index = /* calculate index of row to modify */
  let changes = /* calculate changed value, e.g. { SomeField__c: someValue } */
  this.dispatchEvent(new CustomEvent('change', { detail: { index, changes } }));
}
2
  • I removed the array copy from the parent and created a copy of the shallow using this.privateItemList = value.map(item => {...item}); in the child component, but it doesn't seem to work. But I do agree with the alternative of notifying the parent to update the array. The parent component 'owns' the data and this makes sense. I was hoping to encapsulate this in the child component and notify the parent only when all changes are done.
    – Nagen Sahu
    Commented Jul 10, 2020 at 19:12
  • @NagenSahu You could wait until all changes are made and pass the entire thing back. My example is just one of several ways you might do this.
    – sfdcfox
    Commented Jul 10, 2020 at 19:18

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .