[SalesForce] Lightning web component object reference

I've seen many examples with primitive data types, but I've been trying to propagate array down to my children component and encountered some problems.

My parent component looks like this:

<template>
    <lightning-card title="ListReference">

        <div class="slds-p-around--medium">List: {listString}</div>

        <c-list-reference-inner 
             list={list}
        ></c-list-reference-inner>
    </lightning-card>
</template>

and its javascript:

import {LightningElement, track, api} from 'lwc';
export default class ListReference extends LightningElement {

    @track list = [{},{},{}];

    constructor() {
        super();
        setInterval(() => {
            this.list = [...this.list];
        }, 3000);
    }

    get listString() {
         return JSON.stringify(this.list);
    }
}

This is how my child component listReferenceInner looks like:

<template>
    <lightning-button
            variant="brand"
            label="mutate list"
            class="slds-m-left_x-small"
            title="mutate"
            onclick={mutateListElement}
    ></lightning-button>
</template>

and javascript:

import {LightningElement, api} from 'lwc';

export default class ListReferenceInner extends LightningElement {

    @api list;

    mutateListElement() {
        this.list.forEach(element => element.ttt = 'ttt');
    }
}

What happens is that when the button on the child component is clicked,
the array's elements are updated. I also put setInterval to the parent component's constructor, which just clones the array so the @track catches this change and rerender the component so we can see that the array changes were propagated up from the child component. So far I know this isn't something we want, because LWC uses one-way binding.

Moreover, if I change the decorator on the parent component from @track to @api, the child function mutateListElement quietly dies on the first element.ttt = 'ttt', maybe because of some trap on the element's proxy.

I believe I could solve this with a setter on the child list which would make a copy of that array, but I find that ugly. So my question is how to treat object references when propagated like this?

Thanks for any answers!

Best Answer

The documentation on this topic emphasizes that any child component should treat the value passed from parent as read-only. If you try to mutate the data passed from parent to child, it results in an error (emphasis mine).

NOTE The child component must treat any property values passed from the owner component as read-only. If the child component tries to change a value passed from an owner component, you see an error in the browser console.

As for your question:

I believe I could solve this with a setter on the child list which would make a copy of that array, but I find that ugly. So my question is how to treat object references when propagated like this?

That's in fact the recommended way to pass data from parent to child is:

To communicate down the containment hierarchy, an owner can set a property on a child component. When you add a component in markup, you can initialize public property values in the component based on property values of the owner component. The data binding for property values is one-way.

So its very likely that what you are observing is true. But, if you really don't want to change data in child, then you should not be attempting to pass the data as reference in first place. As a result, your only option to address the issue is to make sure that you pass a copy by setting a property or calling a function on the child component.

Related Topic