[SalesForce] Trying to refresh child LWC Data from parent component new values

I'm trying to pass the new data from a parent LWC to a child. The data is a list of wrapped objects with the following properties: string, string, list, list.

I'm able to send and it renders perfect, but when I click the get data button again the last retrieved data is not rendering or updating the child component.

This is what I have tried.

  • Method 1: using @api set and get
  • Method 2: exposing the child controller method to parents and sending data through parameters.

Method 1

Tried to use @api set and get for the property as described here

Parent HTML

<template if:true={objectRecordsMap}>
    <c-custom-datatable
        object-records-map={objectRecordsMap}
        oncustom_event={handleCustomEvent}
    ></c-custom-datatable>
</template>
<lightning-button
    class="slds-var-m-left_medium"
    variant="brand"
    label="Get Records"
    title="Get records for this contact"
    onclick={searchRecordsForContact}
></lightning-button>

Parent JS

objectRecordsMap;
searchRecordsForContact() {
    returnRecordsForContact({
        contactId: this.theContactId
    })
        .then((results) => {
            this.objectRecordsMap = results;
            this.errors = [];
        })
        .catch((error) => {
            this.objectRecordsMap = undefined;
        });
    this.checkForErrors();
}

Child HTML

<template if:true={records}>
    <template for:each={records} for:item="record">
        <div class="slds-var-m-top_medium" key={record.key}>
            <lightning-card icon-name={record.iconName} title={record.objectPluralLabel}>
                <div class="slds-card__body slds-card__body_inner">
                    <c-rich-datatable
                        key-field={record.key}
                        data={record.recordList}
                        columns={record.columns}
                        onrowaction={handleRowAction}
                        show-row-number-column
                        enable-infinite-loading
                        onrowselection={handleRowSelection}
                    >
                    </c-rich-datatable>
                </div>
            </lightning-card>
        </div>
    </template>
</template>

Child JS

@api set objectRecordsMap(value) {
    this.records = [...value]
}
get objectRecordsMap() {
    return this.records;
}
records;

connectedCallback() {
    if (this.objectRecordsMap.length !== 0) {
        this.buildRecordsDatatables();
    }
}

buildRecordsDatatables() {
    let _map = this.objectRecordsMap;
    let newCol = { type: 'action', typeAttributes: { rowActions: actions } };
    for (let _key in _map) {
        if (_map.hasOwnProperty(_key)) {
            let record = {
                key: _key,
                iconName: _map[_key].iconName,
                objectPluralLabel: _map[_key].objectPluralLabel,
                recordList: _map[_key].recordList
            };
            let cols = [];
            _map[_key].columns.forEach(element => {
                cols.push(element);
            });
            cols.push(newCol);
            record.columns = cols;
            this.records.push(record);
        }
    }
}

Which results and renders in this:

First search (set of data)

enter image description here

Second search.

enter image description here

Console log:

enter image description here

Method 2

Tried to use @api exposedMethod(param) to call the child method imperatively as described here

Parent HTML

<c-retire-contact-datatable
    onreplace_contact_for_selected_records={handleReplace}
></c-retire-contact-datatable>

Parent JS

objectRecordsMap;
searchRecordsForContact() {
    returnRecordsForContact({
        retireContactId: this.contactToRetire
    })
        .then((results) => {
            this.objectRecordsMap = results;
            let datatableCmp = this.template.querySelector('c-retire-contact-datatable');
            datatableCmp.buildRecordsDatatables(this.objectRecordsMap);
            this.errors = [];
        })
        .catch((error) => {
            this.errors.push({ message: error.message.body });
            this.notifyUser('Error', error.message.body, 'error');
            this.objectRecordsMap = undefined;
        });
    this.checkForErrors();
}

Child HTML

<template>
    <template for:each={records} for:item="record">
        <div class="slds-var-m-top_medium" key={record.key}>
            <lightning-card icon-name={record.iconName} title={record.objectPluralLabel}>
                <lightning-button-icon
                    icon-name="utility:change_record_type"
                    slot="actions"
                    label="Replace Contact for selected records"
                    title="Replace Contact for selected records"
                    alternative-text="Replace Contact for selected records"
                    onclick={handleReplaceContactClick}
                ></lightning-button-icon>
                <div class="slds-card__body slds-card__body_inner">
                    <c-icrm-rich-datatable
                        key-field={record.key}
                        data={record.recordList}
                        columns={record.columns}
                        onrowaction={handleRowAction}
                        show-row-number-column
                        enable-infinite-loading
                        onrowselection={handleRowSelection}
                    >
                    </c-icrm-rich-datatable>
                </div>
            </lightning-card>
        </div>
    </template>
</template>

Child JS

records = [];

@api buildRecordsDatatables(wrappedData) {
    let _map = wrappedData;
    let newCol = { type: 'action', typeAttributes: { rowActions: actions } };
    for (let _key in _map) {
        // eslint-disable-next-line no-prototype-builtins
        if (_map.hasOwnProperty(_key)) {
            let record = {
                key: _key,
                iconName: _map[_key].iconName,
                objectPluralLabel: _map[_key].objectPluralLabel,
                recordList: _map[_key].recordList
            };
            let cols = [];
            _map[_key].columns.forEach(element => {
                cols.push(element);
            });
            cols.push(newCol);
            record.columns = cols;
            this.records.push(record);
        }
    }
}

But this way is not rendering the datatables in the child component, but is does process the child method:

enter image description here

I think I'm pretty close with the second method since it's processing the data as I need it.

Best Answer

the push method does not trigger a mutation therefore, the data table is not re-rendered in the ui with the newly fetched data (even though you can see in your logs that a new row has been added)

I would suggest you use map or use a spread operator to copy the changed data to your records property.