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:
- Your
List<Category
andList<CategoryPage>
lists need to live inCategoryService
. Only one version of the truth now. - Call
await categoryService.GetCategories()
to get that list when you need it populated. - Use the lists directly in
NavCategoriesComponent
. - Add an
event
sayRecordListChanged
toCategoryService
. - Register an event handler for that event in
NavCategoriesComponent
and callStateHasChaged
on that event. - Whenever you "Create/Update/Delete" from your modal dialogs, update the
CategoryService
lists and trigger theRecordListChanged
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