Home > Net >  StateHasChanged/this.StateHasChanged doesn't seem to have effect on Blazor Component
StateHasChanged/this.StateHasChanged doesn't seem to have effect on Blazor Component

Time:11-13

I am working on a .net5 Blazor WebApp using the MudBlazor library. I'm trying to create a nav menu that displays certain categories and category pages. But as there is the possibility to add new categories, or add new category pages I need to refresh my component when the info has changed. When calling the function in OnInitializedAsync() this has no problem with rendering the nav menu as it's supposed to. But when calling it again after updating any of the information that it should render, this function no longer seems to do what it's supposed to do, which is re-rendering the component. Now, the easiest solution could be that I can simply refresh the whole page. But this isn't what I want as there is other logic that needs to keep running without being interfered with by the reload of the page. My .razor file looks like the following:

@inject CategoryService categoryService
@inject CategoryPageService categoryPageService

@inherits LayoutComponentBase

<MudText Typo="Typo.h6" Class="px-4" Style="margin-bottom: 10px; margin-top: 10px; text-align:center">Categories</MudText>
<MudDivider />
<MudNavMenu Style="color:white">
@foreach (Category category in NavCategories)
{
    @if(NavCategoryPages.Count > 0)
    {
        @foreach(CategoryPage categoryPage in NavCategoryPages)
        {
            @if(categoryPage.CategoryId == category.Id)
            {
                <MudNavGroup [email protected]>
                    @foreach(CategoryPage categoryPage1 in NavCategoryPages.Where(c => c.CategoryId == category.Id))
                    {
                        <MudNavLink>@categoryPage1.PageName</MudNavLink>
                    }
                </MudNavGroup>
            }
            else
            {
                <MudNavLink>@category.Name</MudNavLink> 
            }
        }
    }
}
</MudNavMenu>

@code
{
    private List<Category> NavCategories = new List<Category>();
    private List<CategoryPage> NavCategoryPages = new List<CategoryPage>();

    protected override async Task OnInitializedAsync()
    {
        await GetCategoriesNav(); //Function that should grab the new information from the database
    }

    public async Task GetCategoriesNav()
    {
        NavCategories = await categoryService.GetCategories();
        NavCategoryPages = await categoryPageService.GetCategoryPages();
        //This delay is to have enough time to view if the StateHasChanged has any effect on first call.
        await Task.Delay(5000);

        StateHasChanged();
    }
}

I've double-checked all the values which they have to display and they show up accordingly in the debug. If you need any extra information don't hesitate to ask.

The first call is being made in:

  • CategorySelector.razor
protected override async Task OnInitializedAsync()
    {
        await GetCategoriesNav();
    }

This call renders the NavMenu as it's supposed to. After that, the only time it's being called somewhere else is when I edit/add a category. This is done in:

  • CategoryAdministration
//These 2 functions are called via a button.
async Task AddCategory()
    {
        Category thisCategory = new();

        var param = new DialogParameters { ["category"] = thisCategory };

        IDialogReference dialog = DialogService.Show<CategoryDialog>("Add Category", param);

        DialogResult result = await dialog.Result;
        if(!result.Cancelled)
        {
            GetCategories();
            //if a category has succesfully been added, it calls the same method which also gets called in the "OnInitializedAsync()"
            await GetCategoriesNav();
        }
    }


    async Task EditCategory(Category category)
    {
        category = await categoryService.EditCategory(category);

        var param = new DialogParameters { ["category"] = category };

        var dialog = DialogService.Show<CategoryDialog>("Edit Category", param);

        DialogResult result = await dialog.Result;
        if (!result.Cancelled)
        {
            GetCategories();
//if a category has succesfully been edited, it calls the same method which also gets called in the "OnInitializedAsync()"
            await GetCategoriesNav();
        }
    }

This here is the only external place this is being called, but CategoryAdministration inherits from Category selector.

CodePudding user response:

StateHasChanged/this.StateHasChanged doesn't seem to have effect on Blazor Component

And why should it have any effect ? It is not a magic method. Incidentally, it is superfluous, as it is automatically called by the framework when called from within OnInitializedAsync

It is not clear how the CategorySelector component is related to the other component, in which the GetCategoriesNav method is defined. However, no matter what the relationship is, you can't call GetCategoriesNav from CategoryAdministration as you do: await GetCategoriesNav(); Didn't you get a compilation error ? Generally speaking, and mind you I cannot provide a clear-cut recipe as I don't have the necessary details about the relationship between the components, you can capture a reference to the component in which the GetCategoriesNav method is defined, and using this reference object to call the GetCategoriesNav method, provided that the CategorySelector component is the parent component of the other component. If no relations exist between the two you can define an event in the CategorySelector component that should be triggered when a data refresh is necessary; that is, instead of executing this: await GetCategoriesNav(); The other component should subscribe to the event by creating an event handler that should call the GetCategoriesNav method. In that case you'll need to call the StateHasChanged method manually.

CodePudding user response:

I assume you have a management page CategoryAdmin.razor with buttons that open Dialogs with an editor - Category Dialog.

Within that page you have a component - I assume called NavCategoriesComponent - that displays the NavCategories.

You click on one of your buttons and edit/add a category, but on exit from the dialog the list in the NavCategories component doesn't update.

You also have a CategoryService that I'm assuming gets the list of categories.

In NavCategoriesComponent you code looks like this:

private List<Category> NavCategories = new List<Category>();
private List<CategoryPage> NavCategoryPages = new List<CategoryPage>();

//OnInit code
       NavCategories = await categoryService.GetCategories();
        NavCategoryPages = await categoryPageService.GetCategoryPages();
        //not required
        await Task.Delay(5000);

and then in your main component you call the following code for add/edit:

            GetCategories();
//if a category has succesfully been edited, it calls the same method which also gets called in the "OnInitializedAsync()"
            await GetCategoriesNav();

So which list are you expecting the second calls to update? The lists in NavCategoriesComponent are a totaly separate. Calling GetCategories() after editing doesn't update the list in NavCategoriesComponent. That only happens when you reload the page. OnInitializedAsync as the name suggests only gets run once.

If that's your basic scenario then:

  1. Your List<Category and List<CategoryPage> lists need to live in CategoryService. Only one version of the truth now.
  2. Call await categoryService.GetCategories() to get that list when you need it populated.
  3. Use the lists directly in NavCategoriesComponent.
  4. Add an event say RecordListChanged to CategoryService.
  5. Register an event handler for that event in NavCategoriesComponent and call StateHasChaged on that event.
  6. Whenever you "Create/Update/Delete" from your modal dialogs, update the CategoryService lists and trigger the RecordListChanged event.

You should never manually call OnInitializedAsync, and you should rarely need to call StateHasChanged.

This Github Repo contains a solution that demos the above principles in the good old Blazor WeatherReport app - Blazr.Demo.DBNotification

  • Related