LWC – @api property does not render on child component

custom-eventerrorlightning-web-componentsparent-to-child

I have a 4 layers LWC component: great-grandparent > grandparent > parent > child. I want to pass an @api property from great-grandparent to display and modify data in child.

Because the property does not display on the parent component so please excuse me for posting the code of grandparent and parent only.

Great-Grandparent Controller

@AuraEnabled
    public static List<Sobject> getDatas(Boolean isRecentlyView, 
                                         List<String> fields, 
                                         String sobjectType, 
                                         String query, 
                                         Integer limitNum){
        try {
            if (limitNum != null) {
                if (isRecentlyView) {
                    query = 'SELECT ' + String.join(fields, ', ') + ' FROM ' + sobjectType + ' WHERE LastViewedDate !=null ORDER BY LastViewedDate DESC ';
                    query += ' LIMIT :limitNum';
                } else {
                    query = query.split('FROM')[1];
                    query = 'SELECT ' + String.join(fields, ', ') + ' FROM ' +  query;
                    query += ' LIMIT :limitNum';
                }
                return Database.query(query);
            } else return new List<SObject>();
        } catch (Exception e) {
            throw e;
        }
    }

Great-Grandparent HTML

<c-grandparent data={parentdata}></c-grandparent>

Great-Grandparent JS

import { LightningElement, api, track, wire } from 'lwc';
import getDatas from '@salesforce/apex/ListviewController.getDatas';

export default class Great-Grandparent LightningElement {

@api limitNum; // receiving value from config xml

@track isLoading = false;
@track fieldcolumns = [];

parentdata = [];
numberOfRecords = '';
isRecentlyView = false;

handleGetData(){
    getDatas({
       isRecentlyView: this.isRecentlyView,
       fields: fields,
       sobjectType: this.sObjectType,
       query: query,
       limitNum: this.numberOfRecords})
       .then(result => {
            console.log(result);
            this.parentdata = result;})
       .catch(e => {
            console.error(e);})
     }
}

Grandparent HTML

<c-parent records={data}
   onlistitemdrag={handleListItemDrag}
   onitemdrop={handleItemDrop}>
</c-parent>

Grandparent JS

@api data // I have not performed any logic for it here, so I only post the property

Parent HTML

<template>
    <ul ondrop={handleDrop}
        ondragover={handleDragOver}
        style="height:70vh; overflow-y:auto;">
        <template for:each={records} for:item="recordItem">
            <c-child record={recordItem} 
                key={recordItem.Id}
                onitemdrag={handleItemDrag}>
            </c-child>
        </template>
    </ul>
</template>

Parent JS

import { LightningElement, api } from 'lwc';

export default class Parent extends LightningElement {
    @api records;

    renderedCallback() {
        console.log("Render call back in parent"); //I log to see how it render
        console.log('this.records',this.records);
    }

    handleDragOver(evt){
        evt.preventDefault()
    }

    handleDrop(){
        //some logic here
        this.dispatchEvent(event)
    }

    handleItemDrag(evt){
        //some logic here
        this.dispatchEvent(event)
    }

}

=== I tried another way to approach it by update enforces this read-only behavior ===

Parent HTML Modified Code

<template>
    <ul ondrop={handleDrop}
        ondragover={handleDragOver}
        style="height:70vh; overflow-y:auto;">
        <template for:each={_records} for:item="recordItem">
            <c-child record={recordItem} 
                key={recordItem.Id}
                onitemdrag={handleItemDrag}>
            </c-child>
        </template>
    </ul>
</template>

Parent JS Modified Code

import { LightningElement, api } from 'lwc';

export default class Parent extends LightningElement {
    _records = [];

    get records() {
        return this._records;
    }
    @api set records(value) {
        this._records = value;
    }

    renderedCallback() {
        console.log("Render call back in parent"); //I log to see how it render
        console.log('this._records',this._records);
    }

    handleDragOver(evt){
        evt.preventDefault()
    }

    handleDrop(){
        //some logic here
        this.dispatchEvent(event)
    }

    handleItemDrag(evt){
        //some logic here
        this.dispatchEvent(event)
    }
}

Result from debug console

this.records 
Proxy(Array)
[[Handler]]: ReadOnlyHandler
[[Target]]: Array(5)
length: 5
[[Prototype]]: Array(0)
[[IsRevoked]]: false

Thank you so much for taking a look at my code

Best Answer

numberOfRecords = '';

Isn't a valid number, so your Apex Code won't parse it correctly, and end up with a null value.

if (limitNum != null) {
  // ...
} else return new List<SObject>();

Since limitNum is null here, you'll return an empty list.

Thus, you don't get any results, and nothing appears at the parent level.

To fix this, provide a valid number.

numberOfRecords = 100;

Further, you probably want to use connectedCallback() to call your method, rather than putting in the class body as a function. This code shouldn't compile at all.

connectedCallback() {
  getDatas({
   isRecentlyView: this.isRecentlyView,
   fields: fields,
   sobjectType: this.sObjectType,
   query: query,
   limitNum: this.numberOfRecords})
   .then(result => {
        console.log(result);
        this.parentdata = result;})
   .catch(e => {
        console.error(e);})
  }
}