[SalesForce] LWC dispatchEvent blocking the wire service call on a component

I am trying to get the records of some object X using wire service by passing a param to wire service. Below is the code,

searchResults.js

isLoading = false;
boatTypeId = '';
@api
searchBoats(typeId) {
    this.notifyLoading(true);
    this.boatTypeId = typeId;
}

@wire(getBoats, {boatTypeId: '$boatTypeId'})
wiredBoats(result) {
    console.log('wired')
    this.boats = result;
    if (result.error) {
        this.boats = undefined;
        this.error = 'Unknown error';
        if (Array.isArray(error.body)) {
            this.error = error.body.map(e => e.message).join(', ');
        } else if (typeof error.body.message === 'string') {
            this.error = error.body.message;
        }
    }
    this.notifyLoading(false);
}

notifyLoading(isLoading) {
    // dispatch doneLoading or loading events based on the status of isLoading
    this.isLoading = isLoading;
    if (isLoading) {
        const loading = new CustomEvent("loading");
        this.dispatchEvent(loading);
    } else {
        const doneLoading = new CustomEvent("doneloading");
        this.dispatchEvent(doneLoading);
    }
}

search.js

// Handles loading event
handleLoading() { 
    this.isLoading = true;
}

// Handles done loading event
handleDoneLoading() { 
    this.isLoading = false;
}

// Handles search boat event
// This custom event comes from the form
searchBoats(event) { 
    const searchBoatTypeId = event.detail.boatTypeId;
    this.template.querySelector("c-boat-search-results").searchBoats(searchBoatTypeId);
}

search.html

 <lightning-layout-item size="12" class="slds-p-top_small slds-is-relative">
    <template if:true={isLoading}>
        <lightning-spinner alternative-text="Loading"></lightning-spinner>
    </template>
    <template if:false={isLoading}>
      <c-boat-search-results onloading={handleLoading} ondoneloading={handleDoneLoading}>
      </c-boat-search-results>
    </template>
  </lightning-layout-item>

Expectation is when searchBoats is called from search.js it will display the loading animation by calling this.notifyLoading(true); (which dispatched loading event handled by search component). And then searchBoats updates the this.boatTypeId which in turn should trigger the wire service to get the this.boats results from getBoats.

What is happening actually is
searchBoats successfully gets called and displays the loading spinner. After that wire service never gets invoked and I get infinite loading spinner (because wire service is never invoked code flow never reaches this.notifyLoading(false) in the wire call)

If I remove this.notifyLoading(true); call completely from searchBoats then everything just works fine, which is why it looks like dispatchEvent for loading spinner is blocking the execution of wire service even when the $boatTypeId value is updated.

Need help understand this behaviour and what is actually causing this to happen. Pretty sure I am doing something wrong here.

Best Answer

There appear to be several issues with this code:

  1. For the searchResults, you should not assign empty string to the boatTypeId initially - a wire is invoked only once all its dynamic parameters have non-undefined values and because of this you are likely causing an unhelpful initial invocation of the wire, during component initialization, before the searchBoats API property is set.
  2. For the searchResults, the searchBoats API is not defined as a property directly or as a property with getter and setter methods.
  3. For the search, this should interact with the searchResults using API property setting through the template instead of directly invoking functions on the child component.
  4. The loading handling presentation of the spinner should be handled by the child component. The problem you have is that the child component gets destroyed in the search template when template if:true={isLoading}.

Try the following changes:

searchResults

isLoading = true;
boatTypeId;

@api get searchBoats() {
    return this.boatTypeId;
}

set searchBoats(typeId) {
    this.boatTypeId = typeId;
    this.isLoading = true;
}

@wire(getBoats, {boatTypeId: '$boatTypeId'})
wiredBoats(result) {
    this.isLoading = false;
    ...
}

The searchResults HTML should be updated to show the spinner when isLoading is true, or the actual results otherwise.

search HTML should just do something like:

<lightning-layout-item size="12" class="slds-p-top_small slds-is-relative">
    <c-boat-search-results ondoneloading={handleDoneLoading} searchBoats={id}>
    </c-boat-search-results>
</lightning-layout-item>

And have an "id" tracked property in the search component that is set to the value to be searched against by the search results.

Related Topic