Lightning Web Components – How to Wait for Chain of Wire Adapters to Settle Before Running Logic

lightning-web-componentslwc-wire-adapterpromises

I'm trying to get the timing of some setup code in an LWC to be reliable. The code needs the opportunity record type Id so it can get the correct picklist values. Then with those picklist values (and some other async data) I'm creating the data structure that my UI needs.

The code I am using is below. While the await for the opportunity record type Id does block, meanwhile the getPicklistValues wire adapter will often have completed for the initial undefined _opportunityRtId value so the next awaitfalls through before the getPicklistValues wire adapter has run again with the correct _opportunityRtId value.

I'm pretty obviously approaching this problem the wrong way. But imperative calls here are not available AFAIK and based on Can we call LWC wire adapters imperatively?. And I get that wire adapters can react to changes in '$...' arguments to offer a form of chaining. And also Chain wire methods together in Lightning Web Components.

Is there a pattern where the code can wait for a chain of @wire calls to settle and then run some code once only? Do you need to set completion flags for each @wire and then when all complete run the code?

_opportunityRtId;

async init() {

    const rtis = await this._opportunityObjectInfo.data.recordTypeInfos;
    this._opportunityRtId = Object.keys(rtis).find(
        rti => rtis[rti].name === '...'
    );

    const results = await Promise.all([
        this._opportunityPicklistValues,
        ...
    ]);

    const r0 = results[0];

    // Initialisation code that uses the picklist values r0.data.values
    // and other async results
}

@wire(getObjectInfo, {
    objectApiName: OPPORTUNITY_OBJECT
})
_opportunityObjectInfo;

@wire(getPicklistValues, {
    recordTypeId: '$_opportunityRtId',
    fieldApiName: OPPORTUNITY_TYPE_FIELD
})
_opportunityPicklistValues;

Best Answer

I restructured the code as below and it works reliably now. More painful to construct than is ideal IMHO.

_opportunityRtId = undefined;
_opportunityPicklistValues;
_contactTitles;

@wire(getObjectInfo, {
    objectApiName: OPPORTUNITY_OBJECT
})
wiredOpportunityInfo({ error, data }) {
    if (data) {
        const rtis = data.recordTypeInfos;
        const rtId = Object.keys(rtis).find(rti => rtis[rti].name === '...');
        this._opportunityRtId = rtId;
    } else if (error) {
        this.error('getObjectInfo error ' + JSON.stringify(error));
    }
}

// Reacts to record type Id
@wire(getPicklistValues, {
    recordTypeId: '$_opportunityRtId',
    fieldApiName: OPPORTUNITY_TYPE_FIELD
})
wiredOpportunityTypePicklist({ error, data }) {
    if (data) {
        // Only interested in the results when the record type Id is defined
        if (this._opportunityRtId) {
            this._opportunityPicklistValues = data.values;
            this._init();
        }
    } else if (error) {
        this.error('getPicklistValues error ' + JSON.stringify(error));
    }
}

async _init() {

    // Imperative data
    if (!this._contactTitles) this._contactTitles = await queryContactTitles();

    // Wired data
    if (!this._opportunityPicklistValues) return;

    // Rest of init logic
    ...
}

PS

In Understand the Wire Service there is:

Properties in the adapterConfig object can’t be undefined. If a property is undefined, the wire service doesn’t provision data.

so it does look like leaving properties undefined should stop the wire service from running before you want it to.