4

It seems like a more comprehensive discussion of LWC async programming would be helpful. Luckily most stuff works if you follow the LWC examples, but once your design falls outside the examples, there isn't much to go on (with respect to async).

I had two situations where setting the active-section-name was unresponsive even though I was @tracking the variable I used to set the active-section-name.

Both issues were fixed by async/await Promise.resolve(), which was the answer in this question: (thank you @sfdcfox!)

Open multiple accordion section using active-section-name not working as expected

Here is the HTML for rendering a list of Task Notes:

    ...
    <lightning-accordion allow-multiple-sections-open active-section-name={activeSections}>
        <template iterator:it={notesViewModel}>
            <lightning-accordion-section key={it.value.Id} name={it.value.Id}
                label={it.value.LabelText}>
                <div class="slds-box slds-theme_shade">
                    <lightning-formatted-rich-text value={it.value.BodyHtml}>
                    </lightning-formatted-rich-text>
                </div>
            </lightning-accordion-section>
        </template>
    </lightning-accordion>
    ...

Here is the wired function that gets a list of Task Notes: (see Promise.resolve())

    ...
    @api recordId;
    @track notesViewModel = [];
    error;
    @track activeSections = [];

    @wire(getNotes, { parentId: '$recordId' })
    async getNotesViewModel(result) {
        if(result.data)
        {
            this.notesViewModelResult = result;
            this.notesViewModel = this.getFilteredNotesViewModel(this.notesViewModelResult.data);
            this.error = undefined;
            await Promise.resolve();
            this.activeSections = this.getInitialActiveSections(this.notesViewModel);
            this.noteSummaryText = this.getNoteSummaryText();
        }
        else if (result.error)
        {
            this.notesViewModel = undefined;
            this.error = result.error;
        }
    }
    ...

Here is a method used to filter the visible Task Notes: (see Promise.resolve())

    async handleNoteTypeFilterChange(event)
    {
        this.noteTypeFilterValue = event.detail;
        this.notesViewModel = this.getFilteredNotesViewModel(this.notesViewModelResult.data);
        await Promise.resolve();
        this.activeSections = this.getInitialActiveSections(this.notesViewModel);
    }

The active-section-name attribute of the lightning-accordion component seems especially sensitive to timing. I tried the rendered and connected callbacks, but neither helped. To be clear, the code above works fine. I'm looking for documentation that clarifies why the additional promises are necessary/appropriate.

What are the clues (other than not working) that adding Promise.resolve() is the answer? (It feels like trial and error.)

Is there any documentation that would help? (I did not find any clues about using Promise.resolve() in this manner.)

1 Answer 1

3

The documentation doesn't really call this out, but I can tell you from experience that the problem lies with two-way properties. Because @api requires that a property be read-only, in order to implement a two-way data flow, you have to use a getter/setter design. Here's the source behind lightning-accordion activeSectionName (as of today, anyways):

@api
get activeSectionName() {
    const openSections = this.privateAccordionManager.openSectionsNames;

    if (!this.allowMultipleSectionsOpen) {
        return openSections.length ? openSections[0] : undefined;
    }

    return openSections;
}

set activeSectionName(value) {
    this._activeSectionName = value;

    if (!this.privateIsSectionLessInLastRender) {
        this.privateAccordionManager.openSectionByName(value);
    }
}

If you really want to know how the sausage is made, check out lightning-base-components. You can npm i lightning-base-components, then check out the source in node_modules/lightning-base-components.

The main point to observe here is that this code immediately tries to open/close sections, instead of waiting for a render cycle itself. This means that if you change the available sections, you need to do that waiting yourself, otherwise the component's don't exist in the DOM and won't be updated accordingly.

In general, if a property acts as a two-way data binding, you should assume that it follows this kind of design, and that waiting a render cycle is necessary in order for the DOM to render.

Unfortunately, you won't find this in the documentation that I'm aware of, but you can consistently predict most of the attributes that require async/await Promise.resolve(), as they will appear to be read/write to the child. There may be a few exceptions, though. I haven't exhaustively mapped out which properties follow this design, I just happen to be aware of a few of them, since I also have run into similar problems in the past.

1
  • Thank you for the fast reply! This is the type of information I was looking for.Thanks, Ron Jan 23, 2023 at 21:24

You must log in to answer this question.

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