0

I am trying to get a value from a list of objects to show up on the html page using the for:each directive. I have a then method that dynamically builds an array of objects that holds an id number and the value, which is the url for a contact record. I think it has to do with the then method being asynchronous, but how else would I get this array of urls/ids to show up on the html for:each directive? Oddly enough, it shows up when I have the j variable implemented on the html page. However, that is something I do not want. It was just used for debugging but then I discovered that it makes the actual object values show up that I do want.

 import { LightningElement, api, wire, track } from 'lwc';
 import { NavigationMixin } from 'lightning/navigation';
 import { getSObjectValue } from '@salesforce/apex';
 import { getRecord, getFieldValue } from 'lightning/uiRecordApi';
 import getContacts from '@salesforce/apex/AccountController.getContacts';
 import ACCOUNTNAME_FIELD from '@salesforce/schema/Account.Name';
 import CONTACTID_FIELD from '@salesforce/schema/Contact.Id';

 export default class RelationshipMap extends NavigationMixin(LightningElement) {
 @track actions = [
     { label: 'Edit', value: 'edit', iconName: 'utility:edit' },
     { label: 'Delete', value: 'delete', iconName: 'utility:delete' },
 ];
 @api recordId;
 urlList = [];
 contacts;
 error;
 j;

 @wire(getRecord, { recordId: '$recordId', fields: [ACCOUNTNAME_FIELD]})
 account;

 @wire(getContacts, { accId: '$recordId' })
 contacts({ error, data }){
    if(data){
        this.contacts = data;
        this.error = undefined;
        Promise.all(
            this.contacts.map((contact) =>
             this[NavigationMixin.GenerateUrl]({
                 type: "standard__recordPage",
                 attributes: {
                     recordId: getSObjectValue(contact, CONTACTID_FIELD),
                     objectApiName: "Contact",
                     actionName: "view",
                 },
             })
        )
     ).then((urls) => {
         //this.urlList = urls;
         for(let i=0; i < urls.length; i++){
             let obj = {id : i, value: urls[i]};
             this.urlList[i] = obj;
             console.log(this.urlList[i]);
         }
         console.log(this.urlList);
         console.log(this.urlList[0].id + ' ' + this.urlList[0].value);
         this.j = this.urlList[0].id + ' ' + this.urlList[0].value;
     });
    } else if(error){
        this.error = error;
        this.contacts = undefined;
        console.log("Error");
    }
 }

 get accountName() {
    return this.account.data ? getFieldValue(this.account.data, ACCOUNTNAME_FIELD) : '';
 }
}

html

 <template>
 <div style="display: block;width: 250px;border: 2px solid rgba(211,211,211,0.9);border-radius:5px;">
     <div style="text-align: center;padding-top: 16px;padding-bottom:6px;">
         <lightning-icon icon-name="standard:account"></lightning-icon>
     </div>
     <div style="text-align: center;margin: 8px;">
     <lightning-formatted-text
     value={accountName}
     class="slds-text-heading_medium">
     </lightning-formatted-text>
     </div>
     <p>URL List</p>
     <template for:each={urlList} for:item="url">
         <p key={url.id}>
             {url.value}
         </p>
     </template>
 </div>
 <svg width="500" height="75"><line x1="125" y1="0" x2="125" y2="350" style="stroke:rgb(211,211,211);stroke-width:4"/></svg>
 
 <div style="background-color:rgba(211,211,211,0.15); width: 250px;display: block;border: 2px solid rgba(211,211,211,0.9);border-radius:5px;">
     <div style="text-align: center;padding-top: 16px;padding-bottom:6px;">
         <lightning-icon icon-name="standard:contact"></lightning-icon>
     </div>
     <div style="text-align: center;padding: 5px 1px;">
         <lightning-formatted-text
         value="Related Contacts"
         class="slds-text-heading_medium">
         </lightning-formatted-text>
     </div>
     <template for:each={contacts} for:item="contact">
         <div key={contact.Id} style="background-color:white;margin:8px;border: 1px solid rgba(211,211,211,0.9);border-radius:5px;" class="slds-p-around_medium lgc-bg"> 
             <lightning-tile
             label={contact.Name}
             href={url}
             actions={actions}
             onactiontriggered={handleAction}
             type="media">
             <lightning-icon slot="media" icon-name="standard:contact"></lightning-icon>
             <p class="slds-truncate" title="Role">Borrower</p>
             </lightning-tile>
         </div>
     </template>
     <p>{j}</p>
 </div>
</template>

2 Answers 2

3

the template directive will re-render the dom if a mutation is detected in your variable, your current approach does not trigger a mutation:

for loop :
this.urlList[i] = obj;

in order for this to work, you probably want to use a map:

this.urlList = urls.map( (url, idx) => { return { id : idx, value: url}}) 

this should trigger a "rerender" in your template

1
  • Thank you this works! Could you explain real simple why my first implementation did not trigger a mutation?
    – Benton
    Commented Feb 25, 2022 at 23:20
1

Note: this is an explanatory answer to support glls answer, because the content was too long to fit in a comment.

In LWC, every time a method is called, all of the properties in the class are checked using strict equality to see if they have changed. Strict equality looks at two references to an object to see if they are the same object.

var a = { hello: 'world' }
var b = a;
console.log(a === b); // true
var c = { hello: 'world' }
console.log(a === c); // false

This sort of check is very fast, as it considers only the references (essentially, memory addresses) of the objects, but can't find mutations on objects, such as changing the contents of an array or changing the properties of an object.

In LWC, to make life easier, you can @track a property. If you do this, changes to an array or object will be automatically detected. In order to do that, you could change your original code to:

@track urlList = [];

This enables reactivity on the property. You should not use @track if your data is not changing, because it does introduce some overhead. For example, you decided to write @track actions = ..., which is unnecessary, because actions never changes in your code.

As a general recommendation, you should use Array.prototype.map to build fresh lists instead of using for loops with Array.prototype.push or setting individual indexes, unless you have performance concerns.

While it's true that those methods are slower and shouldn't be used in many "regular" flavors of JavaScript, cloning objects and arrays are preferable to @track, because the runtime has to go through every reactive variable and check if there are differences, even if there are none, while that check can be avoided by not using @track.

2
  • Thank you sfdcfox. That is a very detailed explanation! Just to see that I understand correctly: so map causes it to re-render because in this case, I am creating a new objects in the map method instead of just mutating existing objects? I also tried to use the map method to go through a list of objects and tried to add a property to each instead of just creating a new object and of course, this also did not work. Your explanation tells me why that doesn't work if I understand correctly.
    – Benton
    Commented Feb 27, 2022 at 18:20
  • @Benton I'd have to see your code, but yes, it sounds like you may have done it wrong. You should definitely be cloning objects via the spread syntax.
    – sfdcfox
    Commented Feb 27, 2022 at 21:56

You must log in to answer this question.

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