Refresh blazor server child component from parent component by using StateHasChanged - c#

Blazor relaod child component
One of my component is like below ( This is a child component in my case )
ListMetaData component / Child Component
<table>
bind the list of items from model to the table
</table>
#code {
List<MetaDataViewModel> model= new List<MetaDataViewModel>();
protected override async Task OnInitializedAsync()
{
await LoadMetaDataList();
}
public async Task LoadMetaDataList()
{
model= dbContextService.fetchFromDb();
}
public void Refresh()
{
StateHasChanged();
}
}
I have a parent component and it is like below
#page "/settings/metadata"
#inject IDialogService DialogService
#inject ISnackbar Snackbar
<MudGrid>
<MudItem >
<MudButton OnClick="#((e) => AddNewmetaDataEvent())"
Class="ma-1">Add new</MudButton>
</MudItem>
<MudItem xs="12" >
<ListMetaData #ref="_listComponent" ></ListMetaData>
</MudItem>
</MudGrid>
#code {
private ListMetaData _listComponent;
async Task AddNewmetaDataEvent()
{
var dialog = DialogService.Show<AddMetaDataDialog>("Add new MetaData");
var result = await dialog.Result;
if (!result.Cancelled)
{
Snackbar.Add("Please wait while the list is getting updated", Severity.Info);
_listComponent.Refresh();
}
else
{
Snackbar.Add("Dialog cancelled without performing any action", Severity.Normal);
}
}
}
From parent component on button click event a dialog is appearing and allowing user to add new item to the database.
On close of dialog i am trying to reload the list of items in child component
But the list is not getting updated for me even after using StateHasChanged(). I have to reload the page to see the modified list of items in the child component
I followed couple of answers from community How to refresh a blazor sub/child component within a main/parent component? but not getting what is not correct in my code

This should work (however see the notes)
#page "/settings/metadata"
#inject IDialogService DialogService
#inject ISnackbar Snackbar
<MudItem >
<MudButton OnClick="#((e) => AddNewmetaDataEvent())"
Class="ma-1">Add new</MudButton>
</MudItem>
#if (_updating)
{
<span>Updating</span>
}
else
{
<MudGrid>
<MudItem xs="12" >
<ListMetaData #ref="_listComponent" ></ListMetaData>
</MudItem>
</MudGrid>
}
#code {
private ListMetaData _listComponent;
private bool _updating;
async Task AddNewmetaDataEvent()
{
_updating = true; // forces UI dom refresh
var dialog = DialogService.Show<AddMetaDataDialog>("Add new MetaData");
var result = await dialog.Result;
if (!result.Cancelled)
{
Snackbar.Add("Please wait while the list is getting updated", Severity.Info);
}
else
{
Snackbar.Add("Dialog cancelled without performing any action", Severity.Normal);
}
_updating = false; // forces UI dom refresh
}
}
Try to avoid updating the child components by capturing the reference. I am aware that UI libraries like SyncFusion etc force this kind of #ref / Refresh() pattern. Avoid it as much as possible and instead pass on the data as parameter . That will save you from these type of manual refreshes.

Related

Blazor causing all children to re-render (firstRender is true) instead of a differential update

I have a blazor element which has a CascadingValue.
<Microsoft.AspNetCore.Components.Forms.EditForm EditContext="#EditContext" Model="#Model" OnValidSubmit="#OnValidSubmitInternal" >
<Microsoft.AspNetCore.Components.Forms.ObjectGraphDataAnnotationsValidator />
<CascadingValue Value="#CurrentFormState" Name="FormState" >
#ChildContent(EditContext!)
</CascadingValue>
</Microsoft.AspNetCore.Components.Forms.EditForm>
Here is the relevant bits in my #code block.
private FormStateContext CurrentFormState { get; set; } = new();
private void SetState(FormState s)
CurrentFormState.State = s;
StateHasChanged();
}
private async Task OnValidSubmitInternal(Microsoft.AspNetCore.Components.Forms.EditContext context)
{
SetState(FormState.Submitting);
try
{
await OnValidSubmit.InvokeAsync();
SetState(FormState.Success);
}
catch (HttpProblemException e)
{
// [...] omitted code.
SetState(FormState.Error);
}
catch (Exception e)
{
// [...] omitted code.
SetState(FormState.Error);
}
finally
{
await Task.Delay(2000);
SetState(FormState.Idle);
}
}
The call to StateHasChanged() seems to render the whole sub-tree of ChildContent from scratch. That is, in OnAfterRenderAsync(bool firstRender), firstRender is true. Without calling StateHasChanged() the cascading value is never updated on descendant components which use it.
Why is my whole subtree being re-rendered when StateHasChanged() is called? No only is this poor performance, but it breaks other javascript interop libraries which mount to an actual element on the page. In my case, stripe.js isn't working because blazor is deleting and recreating the DOM elements instead of just updating via the normal diff.
I've tried adding an #key attribute on the EditForm, but the ChildContent still renders from scratch.
This seems to be a bug in Microsoft.AspNetCore.Components.Forms.EditForm when both EditContext and Model are passed into the element. I fixed this by doing something like the following:
#if (Model != null)
{
<Microsoft.AspNetCore.Components.Forms.EditForm Model="#Model" >
// ....
</Microsoft.AspNetCore.Components.Forms.EditForm>
}
else
{
<Microsoft.AspNetCore.Components.Forms.EditForm EditContext="#EditContext" >
// ...
</Microsoft.AspNetCore.Components.Forms.EditForm>
}
With this, my whole sub tree doesn't render from scratch again.

Is there a way to cache RenderFragment output?

Is it possible to cache the output of a RenderFragment in Blazor WebAssembly?
Specifically, this is to retain components shown intermittently without rendering them to the browser. With "rendering to the browser" here I mean outputting HTML to the browser.
I am trying to get this to work to improve performance in a library I am writing where two-dimensional data is shown in the browser. The resulting grid is virtualized to prevent having tens of thousands of elements in the DOM since having that many elements results in a degraded experience.
When virtualizing, elements outside the view are not rendered only to be rendered when they shift into view.
The caching mechanism should preserve the HTML output of Razor Components such that the components can be removed from and added to the DOM without having to be reinitialized and rerendered.
Currently, I have not found a way to achieve this.
A basic set-up to reproduce what I have tried so far is as follows:
create a Blazor WebAssembly project using the default template without hosting.
Add a Razor Component with the name ConsoleWriter.razor to Shared and set the contents as follows:
<h3>ConsoleWriter #Name</h3>
#code {
[Parameter]
public string? Name { get; set; }
protected override void OnInitialized() {
Console.WriteLine($"ConsoleWriter {this.Name} initialized");
}
protected override void OnAfterRender(bool firstRender) {
if(firstRender) {
Console.WriteLine($"ConsoleWriter {this.Name} rendered");
} else {
Console.WriteLine($"ConsoleWriter {this.Name} re-rendered");
}
}
// Trying to stop rerendering with ShouldRender.
// Does not stop the rendering in scenario's where the component has just been initialized.
protected override bool ShouldRender() => false;
}
Replace the contents of Index.razor with the following code:
#page "/"
<p>Components are showing: #showComponents</p>
<button #onclick="() => this.showComponents=!this.showComponents">Toggle</button>
#* Here the components are in the DOM but can be hidden from view, still bogging down the DOM if there are too many. *#
<div style="#(this.showComponents ? null : "display:none;")">
<ConsoleWriter Name="OnlyHidden" /> #* Does not intialize or rerender when showComponents is toggled *#
</div>
#* Here the components are not in the DOM at all when hidden, which is the intended scenario but this initializes the components every time they are shown. *#
#if(this.showComponents) {
<ConsoleWriter Name="Default" /> #* Intializes and rerenders when showComponents is toggled *#
#consoleWriter #* Intializes and rerenders when showComponents is toggled *#
<ConsoleWriter #key="consoleWriterKey" Name="WithKey" /> #* Intializes and rerenders when showComponents is toggled *#
}
#code {
private bool showComponents = false;
private object consoleWriterKey = new object();
private RenderFragment consoleWriter { get; } = builder => {
builder.OpenComponent<ConsoleWriter>(0);
builder.AddAttribute(1, nameof(ConsoleWriter.Name), "RenderFragment");
builder.CloseComponent();
};
}
When running the project and checking the browser console, you can see which components report being reinitialized or rerendered.
The components can be toggled by clicking the button.
Unfortunately, all of those being removed from and added to the DOM report back whenever being toggled to be shown despite their content never changing.
Does anyone have another idea how to approach this?

Blazor update UI inside component foreach loop

I have Blazor components in a way similar to this:
Main.razor
#foreach (Vehicle vehicle in _vehicles)
{
if (vehicle.Visible)
{
<VehicleComponent Vehicle=#vehicle></VehicleComponent>
}
}
#code {
protected override void OnInitialized()
{
_vehicles = new List<Vehicles>();
// -> _vehicles being filled here
base.OnInitialized();
}
}
VehicleComponent.razor
#if (Vehicle != null)
{
<img src="#(Vehicle.src)"/>
<div id="#(Vehicle.Id)" tabindex="#(Vehicle.tabindex)">
<h3>#(Vehicle.text)</h3>
</div>
}
#code {
[Parameter] public Vehicle Vehicle { get; set; }
}
The problem is that all the VehicleComponents inside the loop are rendered after the loop is finished. But I want the component to completely render, before the next List item will be rendered.
I tried using StateHasChanged() after each item in Main.razor, but I got an infinite loop.
Is there a way I can render each component in the loop after each other (and update the UI)?
If you want the visual effect of 'gradually appearing' items:
protected override async Task OnInitializedAsync ()
{
var temp = new List<Vehicles>();
// -> _vehicles being filled here
_vehicles = new List<Vehicles>();
foreach (var vehicle in temp)
{
_vehicles.Add(vehicle);
StateHasChanged();
await task.Delay(500); // adjust the delay to taste
}
}
but be aware that this is an expensive (slow) way to show them.
Using #key won't improve this but it might help when you change the list later:
<VehicleComponent #key=vehicle Vehicle=#vehicle></VehicleComponent>
I think you are misunderstanding the Render process.
Your Razor code gets compiled into a C# class with your Razor compiled as a RenderFragment (a delegate) - you can see it in the project obj/Debug/Net5 folder structure. The component "Render" events queue this delegate onto the Renderer's Queue. The Renderer executes these delegates and applies changes to the Renderer's DOM. Any changes in the Renderer's DOM get passed to the browser to modify it's DOM.
Henk's answer adds the items slowly to the list. It adds a "Render" event through StateHasChanged and then yields through the Task.Delay to let the Renderer re-render the list on each iteration.

Refresh html table data using Blazor and C#

I have a situation where I have a for loop that creates my html table from my datamodel which gets the data from SQL server express. I would like to know if it is possible to create a auto refresh method where the table data only gets refreshed and not the full page, if not then maybe a method that OnClick button will retrieve the latest data from datamodel and update the table accordingly.
I'm new to blazor and C# so any help would be appreciated, my current page structure currently looks as follows:
#page "/employees"
#using DataLib;
#inject IEmployeeData _db
#if (employees is null)
{
<p style="color:white;"><em>Loading . . .</em></p>
}
else
{
<table class="table" id="myTable">
<thead>
<tr>
<th>Entry Date</th>
<th>Employee</th>
</tr>
</thead>
<tbody>
#foreach (var employee in employees)
{
<tr>
<td>#employee.EntryDate</td>
<td>#employee.POI</td>
</tr>
}
</tbody>
</table>
}
#code{
private List<EmployeeModel> employees;
protected override async Task OnInitializedAsync()
{
employees = await _db.GetEmployee();
}
}
The above works perfect when I'm loading this page and when I do a manual refresh.
Is there a way that you guys can maybe assist me?
Thanks.
Not sure this is your aim butt you could try;
#inject IEmployeeData _db
#if (employees is null)
{
<p style="color:white;"><em>Loading . . .</em></p>
}
else
{
<table class="table" id="myTable">
<thead>
<tr>
<th>Entry Date</th>
<th>Employee</th>
</tr>
</thead>
<tbody>
#foreach (var employee in employees)
{
<tr>
<td>#employee.EntryDate</td>
<td>#employee.POI</td>
</tr>
}
</tbody>
</table>
<button #onclick="GetEmployees"> Refresh Employee List</button>
}
#code{
private List<EmployeeModel> employees;
protected override async Task OnInitializedAsync()
{
GetEmployees()
}
private async void GetEmployees()
{
employees.Clear();
employees = await _db.GetEmployee();
StateHasChanged();
}
Good luck,
You could create a SignalR hub on your server. Inject the hub into your api controllers, use it to signal clients that updates have occurred to the data from the API.
Mathias Z
I not understand why not this answer is not taken for good, but for me is all that i want, StateHasChanged(); because i still not use JavaScript.
public MyConstructor()
{
_My_collection_.CollectionChanged += Change_EventArgs;
}
void Change_EventArgs(object sender, EventArgs e)
{
StateHasChanged();
}
If your aim is just to refresh the data at regular interval then you can make use of Javascript Interop that is supported by Blazor. The set-up documentation is available in this link.
Like said by Mathias Z in this solution you would need a button for this to work.
I can see you have been writing both C# code and HTML in same file however I personally prefer keeping them separately. Coming back to the solution you can make use to JavaScript and c# to periodically refresh your displayable content.
Below are the changes you need to make to make it work.
Code-Behind
using Microsoft.JSInterop; // import this library
using this you can invoke JavaScript methods.
[Inject]
private IJSRuntime JSRuntime { get; set; }
OnAfterRenderAsync and OnAfterRender are called after a component has finished rendering. Element and component references are populated at this point. Use this stage to perform additional initialization steps using the rendered content, such as activating third-party JavaScript libraries that operate on the rendered DOM elements.
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await JSRuntime.InvokeVoidAsync("EmployeeInterop.refreshEmployeeData");
}
// This method will be called on button click.
protected async Task GetEmployees()
{
employees = await _db.GetEmployee();
}
wwwroot
Within this folder we generally keep our web-resources including js libraries. Here, create a javascript file e.g. EmployeeInterop.js and below code.
(function () {
window.EmployeeInterop = {
refreshEmployeeData: () => {
setInterval(() => {
document.getElementById("btnGetEmployeeData").click();
}, 3000);
}
};
})();
The setInterval() method calls a function or evaluates an expression at specified intervals (in milliseconds). You can define your own time of refresh.
_Host.cshtml
Register this script file here.
<script src="EmployeeInterop.js"></script>
EmployeeDetail.razor
<button id="btnGetEmployeeData" #onclick="GetEmployees"> Refresh Employee List</button>
Add this below your table tag. In-case you don't want this button to be visible to the end-user then add a style attribute and set it to display:none.

How I can Load another Razor Component into a Razor Component by a Button-click?

I just want to load a razor component into another razor component when user click search button then I want to show search razor component (page) into a hidden div when the user click hide button then it will be hidden. like inline popup.
Like this:
Main Component:
#page "/test"
<button #onclick="(() => ShowComponent = true)">Show</button>
<button #onclick="(() => ShowComponent = false)">Hide</button>
#if (ShowComponent)
{
<ShowHideComponent></ShowHideComponent>
}
#code {
bool ShowComponent { get; set; } = false;
}
ShowHideComponent.razor:
<div>Show Or Hide This</div>

Categories