Summary:
Is there a way to pass an object from a parent component to a child component so that the child component can use getSObjectValue()
to read field values?
The naïve implementation fails because the child component's property is declared as @api
, which exposes a Proxy object that is incompatible with getSObjectValue()
.
Details:
When reading values from an object returned from an Apex call, the Salesforce recommendation is to import the appropriate fields from the schema and to use getSObjectValue()
to read the field values.
This is documented here:
Import Objects and Fields from @salesforce/schema
However, if you pass an object from a parent component to a child component using the exposed @api
property, then you can no longer use getSObjectValue()
in the child component because @api
exposes a Proxy object, which getSObjectValue()
cannot handle.
Is there a way to pass an object to a child component and still be able to use getSObjectValue()
to read the appropriate field value? If not, is there another way to import the fields from a schema into a child component so that dependency tracking is still performed?
Consider the following parent-child example where the parent component wants to render a bunch of child component elements for each Contact
returned from an Apex call:
Parent Component:
The parent component calls an Apex method imperatively to fetch a list of all Contacts
:
import { LightningElement } from 'lwc';
import getAllContacts from '@salesforce/apex/getAllContacts';
export default class ContactsBrowser extends LightningElement {
@track contacts;
@track error;
handleLoad() {
getContactList()
.then(result => {
this.contacts = result;
})
.catch(error => {
this.error = error;
});
}
}
Parent Template:
The parent template then renders each contact in a child component, passing in the appropriate Contact
through the child's @api
property:
<template>
<template lwc:if={contacts}>
<template for:each={contacts} for:item="contact">
<c-contact-row key={contact.Id} contact={contact}></c-contact-row>
</template>
</template>
</template>
Child Component:
The child component exposes @api contact
and uses getSObjectValue()
to read a field value from the given object. However, this will fail because getSObjectValue()
expects a normal JavaScript object but contact
is actually a Proxy
object.
Using contact.Name
works fine, but then we're no longer taking advantage of the imported field and potentially losing some dependency tracking.
import { LightningElement, api } from 'lwc';
import { getSObjectValue } from '@salesforce/apex';
import NAME_FIELD from '@salesforce/schema/Contact.Name';
export default class ContactRow extends LightningElement {
@api contact;
get name() {
// This fails because `contact` is a Proxy object.
return getSObjectValue(this.contact, NAME_FIELD);
}
}
Edits:
In an attempt to create a simple example, the initial version of this post used the @wire
service instead of calling an Apex method imperatively, like the real code does. This mistake results in very different answers. Apologies for the confusion.