12

This is my version of Dana's question:

I have an LWC component that is dropped into a record page (it accepts the recordId property and this is automatically populated by the flexipage infrastructure) that shows data for the record and based on Master Detail children of that object.

I would like to have it automatically refresh itself when the record details are edited through the record page (using the standard "Record Details" component).

[It would be even better if it could refresh when the related list is updated by adding or deleting children through the "Related Lists" standard component]

The component leverages the uiRecordAPI's getRecord service to obtain the field values it needs from the actual record and uses a custom wired Apex method to query the relevant children using the ID returned by getRecord (this ensures the wired method is called only after getRecord has returned the details).

Is it possible to register a listener against some event from the "Record Details" component (and "Related Lists" component?) so I can then use refreshApex to cause the component to re-query details and re-render itself?

UPDATE:

In case this is a "programmer error", here's some bits from the code I have.

Firstly, the component has these properties:

/**
 * The contextual object's record ID. This will identify the Work Schedule
 * instance for which the component is to present and manage the Working Days.
 */
@api recordId;

/**
 * The contextual object's record details needed for presentation purposes.
 *
 * @type {WorkScheduleRecord}
 */
@track record = {
    id: undefined,
    length: undefined,
    cycle: undefined
};

It then has the following wired function to get the record detail it needs when generating its presentation (this uses two imported fields from the schema and has the dynamic recordId passed to it):

/**
 * Fetches the record details so the component has access to the total and cycle length 
 * values. It relies on the ID of the contextual record so this will be triggered ASAP.
 *
 * @param {Object|undefined} error when specified this contains the error(s) encountered 
 * whilst getting the record. May be undefined
 * @param {Object|undefined} data  when specified this contains the record detail. May be 
 * undefined
 */

@wire(getRecord, {recordId: '$recordId', 
                  fields: [TOTAL_LENGTH_FIELD, CYCLE_LENGTH_FIELD]})
receiveRecord({error, data}) {
    if (error) {
        this.error = WorkSchedule.getErrorMessage(error);
    } else if (data) {
        // Populate the tracked record details. Note that the cycle length may be 
        // unspecified in which case it is treated as being set to the same value as the 
        // length. The length must be specified
        this.record = {
            id: this.recordId,
            length: data.fields.namespace__Length__c.value,
            cycle: data.fields.namespace__CycleLength__c.value 
                || data.fields.namespace__Length__c.value
        };
    }
}

Note that this chains to the following wired function call, but only using the ID from the record retrieved:

/**
 * Fetches the working days related to the component's record. This relies on the ID from 
 * the fetched record data, rather than the contextual record ID, to ensure that the total 
 * and cycle lengths are already known before the data is fetched.
 *
 * @param {GetWorkingDaysResponse} response holds either the error(s) encountered whilst 
 * fetching the data or the data itself (or neither in certain cases)
 */
@wire(getWorkingDays, {workScheduleId: "$record.id"})
receiveWorkingDays(response) {
    // Store the response for when the cache needs to be invalidated
    this._workingDays = response;
    let error = response.error;
    let workingDaysByOffset = response.data;

    if (error) {
        this.error = WorkSchedule.getErrorMessage(error);
    } else if (workingDaysByOffset) {
        ... // Lots of processing to set up the visual presentation data
    }
}

If I edit the record to change the Total Length or Cycle Length values this isn't causing getWorkingDays to be invoked. Even if I add the following to the receiveRecord function (after updating this.record) nothing happens:

if (this._workingDays) {
    refreshApex(this._workingDays);
}

If I get this working, it is only half of the solution since I also want to refresh based on addition of new children...

4 Answers 4

16

ADDED

wire service getWorkingDays is dependent on record.id. So, this wire will invoke again only when record.id changes. But, although getRecord service is invoked every-time record is updated, its id never changes and so getWorkingDays is never getting invoked again.

You can use imperative apex call and use it inside getRecord function or you can try refreshApex inside getRecord


OLD ANSWER

getRecord from uiRecordApi is based on lightning data services and so it will be invoked automatically when the record is updated.

Sample JS code:

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

export default class Poc extends LightningElement {
    @api recordId;
    @track account;

    @wire(getRecord, { recordId: '$recordId', fields: [ 'Account.Name', 'Account.Phone' ] })
    getaccountRecord({ data, error }) {
        console.log('accountRecord => ', data, error);
        if (data) {
            this.account = data;
            this.processRelatedObjects();
        } else if (error) {
            console.error('ERROR => ', JSON.stringify(error)); // handle error properly
        }
    }

    processRelatedObjects() {
        console.log('processRelatedObjects for => ', JSON.stringify(this.account));
        // further processing like refreshApex or calling another wire service
    }
}

Proof:

enter image description here


Added based on comments:

Its working even for related list:

enter image description here

2
  • Comments are not for extended discussion; this conversation has been moved to chat.
    – Adrian Larson
    Commented Aug 22, 2019 at 13:55
  • I get this to work in Lightning Experience, but not in Communities as a Partner Community User profile (or as a logged in Admin user in the same Community), is there a limitation I am missing here?
    – erikduvet
    Commented Jan 29, 2020 at 21:39
6

Given @salesforce-sas's answer (patiently built up iteratively with me) the following points should be noted:

My implementation's chaining of wired service calls was broken and here's why...

As a bit of background, when you set up a tracked property you have basically two choices:

  1. Have tracked changes trigger processing if the property itself changes by declaring it like:

    @track someProperty;

  2. Have tracked changes trigger processing if values of sub-properties change by declaring it like:

    @track someProperty = { knownSubProperty1: undefined, knownSubProperty2: undefined };

HOWEVER, if you use one of the properties from that tracked property as a dynamic parameter in a wired service call then it appears the wire is only invoked when the actual value of that property changes.

In my case the property value was not changing, after the first call, since it was effectively set to the record ID for the page, which (of course) doesn't change over the lifetime of the page.

The solution, for me, was to convert the wired call to my getWorkingDays Apex method into an imperative call and make this service uncached. This impacts the code shown in the question as follows. Firstly the handling of retrieving the record details becomes:

@wire(getRecord, {recordId: '$recordId', fields: [TOTAL_LENGTH_FIELD, CYCLE_LENGTH_FIELD]})
receiveRecord({error, data}) {
    if (error) {
        this.error = WorkSchedule.getErrorMessage(error);
    } else if (data) {
        // Populate the tracked record details. Note that the cycle length may be unspecified in which case
        // it is treated as being set to the same value as the length. The length must be specified
        this.record = {
            id: this.recordId,
            length: data.fields.namespace__Length__c.value,
            cycle: data.fields.namespace__CycleLength__c.value || data.fields.namespace__Length__c.value
        };

        // Refresh the view of the data
        this._refreshView();
    }
}

Next, the original wired call to get the details for presentation is converted to the new private _refreshView function:

/**
 * Fetches the working days related to the component's record. This only does anything when  the ID from the
 * fetched record data is set to ensure that the total and cycle lengths are already known before the data
 * is fetched.
 *
 * @private
 */
_refreshView() {
    if (this.record.id) {
        // Imperatively obtain the working days detail
        getWorkingDays({workScheduleId: this.record.id}).then(workingDaysByOffset => {
            if (workingDaysByOffset) {
                ... // Lots of processing to set up the visual presentation data
            } else {
                // Clear the visuals
                ...
            }
        }).catch(error => {
            this.error = WorkSchedule.getErrorMessage(error);
        });
    }
}

With this, when changes are made to the record, the getRecord wire is invoked and this in turn explicitly calls the _refreshView function. This then imperatively invokes the service to get the child objects to present visually in the component.

Note that I found that this did not resolve handling of updates to instances in the related list, rather than through my component, likely because these instances are not fetched, in the component, using getRecord but rather are returned by getWorkingDays. This is a minor UX glitch that I'm going to live with...

2

I have answered a similar question here https://salesforce.stackexchange.com/a/363996/108140

Basically the idea is to use combination of getRecord and wiredMethod to achieve this. getRecord is called when record is saved & then you can chain a wired method inside it. LastModifiedDate is used to chain these two since it gets updated every time record is saved.

import { LightningElement, wire, api, track } from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';
import someMethod from '@salesforce/apex/SomeCtrl.someMethod';
export default class SomeCustomComponent extends LightningElement {
@api recordId;
@track account;
lastModifiedDate;

@wire(getRecord, { recordId: '$recordId', fields: [ 'Account.Name', 'Account.Phone' ] })
getaccountRecord({ data, error }) {
    console.log('accountRecord => ', data, error);
    if (data) {
        this.account = data;
        this.lastModifiedDate = data.lastModifiedDate;
    } else if (error) {
        console.error('ERROR => ', JSON.stringify(error));
    }
}

error;
@wire(someMethod, {recordId : '$recordId', lastModifiedDate : '$lastModifiedDate'})
wiredSomeMethod({ error, data }) {
    if (data) {
        // code goes here
    } else if (error) {
        this.error = error;
    }
}
}
1
  • This would be better implemented using imperative apex, IMHO. My reasoning; you are adding an unnecessary parameter just to cause the wire infrastructure to have a cache miss (rather than cache hit). This means the client-side cache will have multiple entries unnecessarily. Better to use imperative without caching as I have suggested in my answer.
    – Phil W
    Commented Mar 30, 2022 at 17:56
0

Apart from refreshApex, if you want to refresh the record page explicitly, you can use getRecordNotifyChange. https://developer.salesforce.com/docs/component-library/documentation/en/lwc/reference_get_record_notify

You must log in to answer this question.

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