13

From SF docs:

There are some limitations on the depth of changes tracked for rerendering in reactive properties. The tracking depth depends on the type of the reactive property.

Lightning Web Components tracks changes to the internal values of these types of reactive properties:

  1. Primitive values
  2. Plain objects created with {…}
  3. Arrays created with []

Also SF does not track for complex types like Date.

Now what I wanna do is track an Array of complex object. If a new element is added subtracted from the array re-render the UI. Classic Data - table example.

So I declared my Markup:

<template>
    <h1>Hello </h1>
    <lightning-datatable
                key-field="id"
                data={data}
                columns={columns}
                is-loading={tableLoadingState}>
            </lightning-datatable>

            <lightning-button variant="brand" label="Add row" title="Add row" onclick={addRow} class="slds-m-left_x-small"></lightning-button>
</template>

JS:

import { LightningElement, track } from 'lwc';
const columns = [
    { label: 'Label', fieldName: 'name', type:"text"},
    { label: 'Website', fieldName: 'website', type: 'text' },
    { label: 'Phone', fieldName: 'phone', type: 'text'  }
];
export default class App extends LightningElement {
    @track data = [{name:'Mylabel' , website:'google.com' ,phone :'74126478963',amount : 45  }];
    @track columns = columns;
    @track tableLoadingState = false;

    addRow(){
        console.log('add row called');
        this.data.push({name:'Mylabel2s' , website:'2google.com' ,phone :'74126478963',amount : 45 });
        console.log(JSON.stringify(this.data));
        //this.data =[];

    }


}

When I save and try running this I get the message as shown in docs.

[LWC warning]: Property "_def" of [object:vm undefined (0)] is set to a non-trackable object, which means changes into that object cannot be observed.
<lightning-primitive-header-factory>

which means I cannot track the row data for lightning-datatable, which also means when I add or remove rows I would be unsuccessful.

I could not find a solution to add row in datatable and get it updated back in the markup, I searched dev guide and recipe book. Is this a limitation? or there is a workaround?

Playground Link

3
  • Just curious. Where are you getting this error saying "non-trackable object"? I am not getting any error when I tried in my Org although nothing is happening when I click on Add row button and no errors in browser console also.
    – javanoob
    Mar 13, 2019 at 21:07
  • I have provided playground link to demo this. Mar 13, 2019 at 21:18
  • Thanks. Interesting, it is not showing that warning message when I tried in my org but only showing in playground.
    – javanoob
    Mar 13, 2019 at 21:22

5 Answers 5

17

Well, I was able to fix this by using the ES6 Spread operator, which basically splits the array into the individual element and then recreate array...

addRow(){


        this.data = [...this.data ,{name:'Mylabel2s' , website:'2google.com' ,phone :'74126478963',amount : 45 } ];

    }

Playground link

3
  • I'm assuming the older style concat would also do the trick since it actually replaces the whole value. Indeed, that's exactly what you are doing here. It isn't actually tracking a change to a nested value, but rather to the whole array.
    – Phil W
    Oct 2, 2019 at 10:21
  • 2
    Please do not use spread or concat when is not needed, those operations are O(n) which are not the most performant operations to exercise. Push work just fine: developer.salesforce.com/docs/component-library/tools/…
    – Diego
    Jan 24, 2020 at 16:39
  • 4
    @Diego Spread (...) actually wrks best when working with @track on arrays. push is not very reliable in LWC for this when working with tables and complex objects
    – Ronnie
    Apr 5, 2020 at 23:37
11

Going through the docs and based on a quick test it seems you can only track changes to the internal values on Arrays created with []. Seems push() does not have the same effect but assigning using [] notion is.

In summary, you will need to recreate the array and assign the values using [] notion.

I was able to get this working by utilizing it as below:

this.data = [
             this.data[0], 
             {name:'Mylabel2s' , website:'2google.com' ,phone :'74126478963',amount : 45 }
];
2
10

Please avoid spread operators unless you really want to concatenate two arrays purposely, spread operators are O(n), which has an non negligible effect on performance.

The push() operation should be as observable as assigning a new array if the property is @track.

https://developer.salesforce.com/docs/component-library/tools/playground/V4r9CeGW/2/edit

2
5

If you end up here, sounds like Spring 20 may have finally addressed this issue. According to the release notes, track for primitives is actually going away, and will only be needed for this type of use case (changes to object properties or arrays) Observe an Object’s Properties or an Array’s Elements

There is still one use case for @track. When a field contains an object or an array, there’s a limit to the depth of changes that are tracked. To tell the framework to observe changes to the properties of an object or to the elements of an array, decorate the field with @track.

So the above solutions definitely work, but I would try the Spring 20 solution before you implement those...

2
  • 3
    As of Winter '21, you still need to use spread operator to see UI changes to an updated array with .push()
    – Mike G.
    Sep 18, 2020 at 0:51
  • @MikeG. Hey Mike, I'm trying to update the selectedRows attribute on every row selection event, but for some reason whenever I set the selectedRows to [] (or empty) at the beginning of the handlerowselection function, the UI doesn't reflect any change to the selectedRows array
    – Franz
    Jan 25, 2021 at 19:11
1

I got this to work without the spread operator. You can just clone the object array by JSON stringify and parse, update the object array then put it in the variable used by the table.

let data = JSON.parse(JSON.stringify(this.tableData));
/* do anything here like pushing another row */
data.push(newTableRow);
this.tableData = data;

You must log in to answer this question.

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