1

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.

3

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 '?!';
    }

}
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.