0
votes

I have a Service that contains a catalog of items. The items depends on the Service's active company.

// Tell Service to refresh in context of Company 1
await Service.SetActiveCompany(1);
// Get Catalog (which will be in the context of Company 1)
Catalog catalog = Service.Catalog;

// Tell Service to refresh in context of Company 2
await Service.SetActiveCompany(2);
// Get Catalog (which will be in the context of Company 2)
catalog = Service.Catalog;

The above works fine in isolation. The Catalog is refreshed with items from the appropriate company.

In my app I have a Layout that includes dropdown to select active company. When this changes I call:

await Service.SetActiveCompany(dropdown.selectedCompanyId);

In the Service, I refresh the catalog and raise an event

public class CompanyChangedEventArgs : EventArgs { }
public class Service
{
    public event EventHandle<CompanyChangedEventArgs> CompanyChanged;
    protected void OnCompanyChanged() 
        => CompanyChanged?.Invoke(this, new CompanyChangedEventArgs());
    public Catalog Catalog { get; private set; }
    public async Task SetActiveCompany(int companyId)
    {
        Catalog = await API.GetCatalogForCompany(companyId);
        OnCompanyChanged();
    }
}

In a parent component (using the layout above) I catch the event

<CatalogSectionA />
<CatalogSectionB />
@code {
   protected override void OnInitialized()
   {
       Service.CompanyChanged += HandleCompanyChanged;
       base.OnInitialized();
   }
   HandleCompanyChanged(object sender, EventArgs e) => StateHasChanged();
}

I expect this to cause CatalogSectionA and CatalogSectionB components to refresh.

CatalogSectionA

<div>
  @foreach (Item i in Catalog.SectionA.Items)
  {
    <div>@i.Name</div>
  }
</div>
@code {
  Catalog Catalog => Service.Catalog;
}

CatalogSectionB

<div>
  @foreach (Item i in Catalog.SectionB.Items)
  {
    <div>@i.Name</div>
  }
</div>
@code {
  Catalog Catalog => Service.Catalog;
}

I had expected calling StateHasChanged() in the parent component to force re-render of CatalogSectionA and CatalogSectionB, but they are not.

I could put a Refresh method on each and call those from parent when company change event is handled, but I thought that would not be necessary with Blazor and StateHasChanged() would do the trick?

1
Can you try calling StateHasChanged inside an InvokeAsync? I.e. HandleCompanyChanged(…) => InvokeAsync(() => StateHasChanged()) - poke
Gave that a go. And also tried making HandleCompanyChanged async and 'awaiting' the InvokeAsync call, but not change. - Neil W
I should clarify that my code above is what I thought would be minimal to show the issue. Actually, the call to SetActiveCompany is async as it calls an API async. Does that change the analysis of the original problem? Have updated question to reflect this. - Neil W
Ah, didn’t read enough earlier; calling StateHasChanged in a parent component will not cause child components to re-render. That would be very expensive. The changed state only applies to the current component so that component will check whether it needs to re-render anything. Since the child components are still the same from the outside (no parameters changed or anything), the parent will not cause the children to rerender. – I would suggest you to pass the Catalog as a parameter to the children. That makes the data flow more explicit and will resolve your issues. - poke
Thanks for the confirmation, @poke. Passing the Catalog down as a Cascading Value for all subcomponents that need it was what I went for, instead of having the sub-component get it directly from the service. Glad to hear a second opinion confirming that approach made sense. Thanks for your help. - Neil W

1 Answers

1
votes

Having put a 'redundant' Cascading Parameter on the Child Components it works.

There were no parameters passed at all as the state (parameters) was being managed in the Service and I was expecting StateHasChanged() to prompt child components to re-render and go back to service.

So, my conclusion is that if I call StateHasChanged() on the parent component, then child components will only be re-rendered if Blazor detects a 'dependency' in the child component on the change in the parent component.

If there are no dependencies (i.e. no parameters or cascading parameters), then it looks like Blazor assumes that no changes will be required in the child component when state changed in the parent component.