Search code examples
c#.netblazor-webassembly

blazor webassembly Why I am getting infinite looping between Page and subComponent?


My app is in VS2022 C# Blazor WASM-hosted with .Net 6. I have a blazor page with a sub-Component that has functionality to allow the PAGE to see a select-box for Active/Inactive (two options) and a select-box for a list of Customer names (abbreviated).

The PAGE looks like this:

enter image description here

The TOP rectangle is the sub-component that "acts" like a "function" that returns (via CALLBACK) the selected "CustomerID" (shown is 'ARCTB'). The sub-component reacts to the "select-choice" (@onchange=) by CALLBACK-parameter to the parent form with the 'selected' CustomerID so the parent-page can fetch the vehicles for that selected customer.

On the initial sub-component OnParametersSetAsync(), where the list of customers and first CustomerID are determined, the CALLBACK to the PAGE is made using the List[0] element for CustomerID. At this point at the PAGE, a re-render takes place for both the PAGE and the sub-component. That causes the sub-component's OnParametersSetAsync() to fire again and the CALLBACK, again for the infinite loop.

The SECOND rectangle is part of the PAGE-html and allows the user to select active or inactive vehicles, that filters the list of customer vehicles to that of _listOfAllVehicles( v => v.IS_ACTIVE == true-or-false).

The problem of infinite-loop occurs because the CALLBACK to the PAGE forces re-renders and then the sub-component performs another CALLBACK from the OnParametersSetAsync() method. This is the infinite-loop problem I seek an answer.

Being new to the latest Blazor, I am not sure if ShouldRender() could be used on the PAGE or sub-component or both? Maybe using a sub-component "flag" to denote "OkToCallback" to limit repeated CALLBACK calls.

Your comments, questions and answers are welcome.

As requested by @Marius, the sub-component code is shown below. I have included a TEST for '_IsLoading' that STOPPED the infinite-loop.

Thank you @Marius for the ANSWER. :-)

protected override async Task OnParametersSetAsync() {
    // Retain the Parameters.
    _LoggedInCustomerName = pLoggedInCustomerName;
    _LoggedInUserRoleName = pLoggedInRole;

    _IsUserNotFCI = (_LoggedInUserRoleName != "FCI");
    _IsActiveCustomers = (_ddlActive_Value == 1);

    await Task.Delay(10);
    _ddlCustomer_Value = RefreshCustomerList(_ddlActive_Value);

    if (_ddlCustomer_Value > 0) {

        // Set to first-option (index) (refer to wwwroot\index.html -- see OnAfterRender()
        if (_IsLoading) {
            // Update the selected customer.
            ChangeEventArgs evntArgs = new ChangeEventArgs();
            evntArgs.Value = _ddlCustomer_Value.ToString();
            await SelectedCustomerChanged(evntArgs);
            _IsLoading = false;
        }
    }

    base.OnParametersSet();
}

private int RefreshCustomerList(int pCustomersActiveFlag) {
    // Refresh the customer-list based on the 'ddlActiveFilter' ACTIVE-STATUS and USER-ROLE.
    // Return the zero-th element's UID_CUSTOMER of the list of customers.
    bool isActiveFlag = (pCustomersActiveFlag==1);
    int retValue = 0;
    if (_ListAllCustomers != null) {
        // When LOGGEDIN-USER is NOT FCI, reduce the list to ONLY the loggedin-user customer.
        if (_IsUserNotFCI) {
            // This returns a list of a single <Customer> record.
            _ListFilteredCustomers = _ListAllCustomers.Where(c => (c.TXT_CUSTOMER_NAME == _LoggedInCustomerName)).ToList();
        } else {
            // This returns a list of all ACTIVE or INACTIVE customers based on 'pCustomersActiveFlag' => 'isActiveFlag'.
            _ListFilteredCustomers = _ListAllCustomers.Where(c => (c.BOOL_IS_CUST_ACTIVE == isActiveFlag)).OrderBy(r => r.TXT_CUSTOMER_ABBREV).ToList();
        }

        // Set the 'ddlCustomer_Value' to the UID of the first-customer in the list.
        retValue = _ListFilteredCustomers[0].UID_CUSTOMER;
    }
    return retValue;
}   

private async Task SelectedCustomerChanged(ChangeEventArgs e) {  // This is the event after selection is made.
    int iArgValue = Convert.ToInt32(e.Value);
    _ddlCustomer_Value = Convert.ToInt32(e.Value);

    Customer customer = _ListFilteredCustomers!.Where(rec => rec.UID_CUSTOMER == _ddlCustomer_Value).FirstOrDefault();
    if (customer != null) {
        _SelectedCustUID = customer.UID_CUSTOMER;
        _SelectedCustName = customer.TXT_CUSTOMER_NAME;
    }


    // Callback to Parent.
    await OnCustomerSelection.InvokeAsync(_ddlCustomer_Value);

}   

Solution

  • OnParametersSetAsync and OnParametersSet will fire whenever a parameter changes, even if the value is the same as before. You can write some code to avoid this.

    Example: Is previous parameter value the same as current ? skip load : load

    private string _cachedValue;
    
    [Parameter]
    public string parameterValue { get; set; }
    
    protected override async Task OnParametersSetAsync()
    {
        if (parameterValue != _cachedValue)
        {
            _cachedValue = parameterValue;
            LoadComponent();
        }
    }
    
    private void LoadComponent() {
        //...
    }
    

    The above is just an example of one method of doing this. Use logic that suits your code.