3

I'm new to LWC. And after going through the entire "Build Lightning Web Components" trail, I must still be missing something fundamental about how the uiRecordApi getRecord method works.

When using @wire(getRecord, ...), I get back an object with data as expected. But I can't access that data in the way I'm used to. Dot notation, Object.keys(), and even JSON.parse(JSON.stringify(Object)) are failing in ways that suggest that the object is undefined in some cases.

I have in my Javascript controller:

import { LightningElement, api, track, wire } from 'lwc';
import { getRecord, getFieldValue } from 'lightning/uiRecordApi';

// ...

import APP_NAME from '@salesforce/schema/Application__c.Name';
// etc...

export default class myApplicationThing extends LightningElement {
    @api recordId; // Application__c.Id
    @track app; // Application__c record
    @track appName;
    // ...
    @wire(getRecord, { recordId: '$recordId', fields })
    loadRecord ({ error, data })   {        
        if (data) {
            this.app = data;
            this.error = undefined;
            this.appName = getFieldValue(data, APP_NAME); // I can use the value
            // of appName just fine if I get it this way, e.g. with {appName}
            // in the HTML template
            // ...
        } else if (error) {
            // ...
        }
    }
    
    // Called from a {testMe} in the HTML template
    get testMe() {
        console.log(this.app); // returns a Proxy
        console.log(JSON.stringify(this.app)); // returns a JSON as expected:

        /* 
        {
            "apiName": "Application__c",
            "childRelationships": {},
            "fields": {
                "Name": {
                    "displayValue": null,
                    "value": "A-0801096"
                },
                "Service_Area__c": {
                    "displayValue": null,
                    "value": "UT"
                },
                ...
            },
            ...
        } */
        
        console.log(this.app.fields.Name.value); // throws error:

        /* afterRender threw an error in 'c:myApplicationThing' 
        [Cannot read property 'fields' of undefined] */

        console.log(this.app.keys()); // throws error:

        /* Cannot read property 'keys' of undefined */

        console.log(JSON.parse(JSON.stringify(this.app))); // throws error:

        /* Unexpected token u in JSON at position 0 */
        
        return '?!';
    }

I could theoretically use getFieldValue to get all the field values on my record, but I'm building up a large form with lots of fields, and trying to nest components within components according to best practice. So in reality this would mean calling out each separate field a whole bunch of times in different places in a way that is super verbose and hard to maintain. I'd much rather pass an object around vs. passing around dozens of primitives.

Also... I read this thread, and while Kevin's answer seems to be getting at something, I'm still thrown by the fact that my first 2 console log examples above do return the value I was expecting.

Am I way off the deep end? What's the right way to go about this?

Solution

By the time I got the answer from @BritishBoyinDC, I had been pondering the nature of ansync wired data, and I solved my problem by wrapping the entire component HTML like so:

<template>
    <template if:true={app}>
        ...
    </template>
</template>

Everything inside that if:true directive that relies on the wired record data holds it horses and waits for the data to show up. :-)

By the way, console.log(this.app) and console.log(JSON.stringify(this.app)) work because you can both log and JSON-stringify an undefined object in JavaScript without incurring an error. On the other hand, if you try to access properties on an undefined object, an error comes up.

1

1 Answer 1

4

I am wondering if this is a timing issue, where you are calling testMe before the getRecord is finished, though I'd need to see your template to be sure. But I re-created your code in a scratch org, and it worked ok when I used this as my template, but without the if:true, both Name and Service Area are undefined

<template>
    <template if:true={app}>
    Test Me: {testMe}
    </template>
</template>

And my controller:

import { LightningElement, api, track, wire } from 'lwc';
import { getRecord, getFieldValue } from 'lightning/uiRecordApi';

import APP_NAME from '@salesforce/schema/Application__c.Name';
import APP_SERVICEAREA from '@salesforce/schema/Application__c.Service_Area__c';

export default class StackAppRecord extends LightningElement {
    @api recordId; // Application__c.Id
    @track app; // Application__c record
    @track appName;
    // ...
    @wire(getRecord, { recordId: '$recordId', fields: [APP_NAME,APP_SERVICEAREA] })
    loadRecord ({ error, data })   {
        if (data) {
            this.app = data;
            this.error = undefined;
            console.log('got data');
        } else if (error) {
            // ...
        }
    }
    // Called from a {testMe} in the HTML template
    get testMe() {
        console.log(this.app); // returns a Proxy
        console.log(JSON.stringify(this.app)); // returns a JSON as expected:
        console.log(this.app.fields.Name.value); // shows Name Value:
        console.log(this.app.fields.Service_Area__c.value); // show Service Value:
        return '?!';
    }

}

You must log in to answer this question.

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