I have a really basic piece of code in Blazor Server .NET 8. I am not an expert on Blazor (yet), but more a backend developer in C# and .NET. My work allows me to learn Blazor and I can get far, but I can't seem to figure out how to update the UI, which seems pretty important.
I have seen this code a lot on my search of my problem:
@using System.Timers
@page "/testing"
Counter value is: @currentCount at @DateTime.UtcNow.ToString("HH:mm:ss")
@code{
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
Console.WriteLine($"Count incremented: {currentCount}");
}
private Timer timer;
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
timer = new Timer();
timer.Interval = 1000;
timer.Elapsed += OnTimerInterval;
timer.AutoReset = true;
// Start the timer
timer.Enabled = true;
}
base.OnAfterRender(firstRender);
}
private void OnTimerInterval(object sender, ElapsedEventArgs e)
{
IncrementCount();
InvokeAsync(() => StateHasChanged());
}
public void Dispose()
{
// During prerender, this component is rendered without calling OnAfterRender and then immediately disposed
// this mean timer will be null so we have to check for null or use the Null-conditional operator ?
timer?.Dispose();
}
}
Whatever I do, the UI is never updating the @currentCount value! The OnTimerInterval is hitting every second, so that works. But the InvokeAsync(() => StateHasChanged()); doesn't work. The StateHasChanged() (without the async) doesn't work either.
What I have tried:
What am I doing wrong??
Update: StackOverflow gave a suggestion for another post:
Blazor-Server side UI Updating does not work or only partially
This answer didn't work either.
I have a really basic piece of code in Blazor Server .NET 8. I am not an expert on Blazor (yet), but more a backend developer in C# and .NET. My work allows me to learn Blazor and I can get far, but I can't seem to figure out how to update the UI, which seems pretty important.
I have seen this code a lot on my search of my problem:
@using System.Timers
@page "/testing"
Counter value is: @currentCount at @DateTime.UtcNow.ToString("HH:mm:ss")
@code{
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
Console.WriteLine($"Count incremented: {currentCount}");
}
private Timer timer;
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
timer = new Timer();
timer.Interval = 1000;
timer.Elapsed += OnTimerInterval;
timer.AutoReset = true;
// Start the timer
timer.Enabled = true;
}
base.OnAfterRender(firstRender);
}
private void OnTimerInterval(object sender, ElapsedEventArgs e)
{
IncrementCount();
InvokeAsync(() => StateHasChanged());
}
public void Dispose()
{
// During prerender, this component is rendered without calling OnAfterRender and then immediately disposed
// this mean timer will be null so we have to check for null or use the Null-conditional operator ?
timer?.Dispose();
}
}
Whatever I do, the UI is never updating the @currentCount value! The OnTimerInterval is hitting every second, so that works. But the InvokeAsync(() => StateHasChanged()); doesn't work. The StateHasChanged() (without the async) doesn't work either.
What I have tried:
What am I doing wrong??
Update: StackOverflow gave a suggestion for another post:
Blazor-Server side UI Updating does not work or only partially
This answer didn't work either.
If you deploy a Blazor Web App template solution with RenderMode: Server and Interactivity : Global, your code will work.
However, doing configuration stuff and mutating state in OnAfterRender{Async}
is smelly code. See Blazor - should I read in my data in OnInitializedAsync() or OnAfterRenderAsync(true).
Here's a refactored version of your code:
@page "/"
@using System.Timers
@implements IDisposable
<PageTitle>Home</PageTitle>
<h1>Hello, world!</h1>
Counter value is: @currentCount at @DateTime.UtcNow.ToString("HH:mm:ss")
@code {
[CascadingParameter] private HttpContext? httpContext { get; set; }
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
Console.WriteLine($"Count incremented: {currentCount}");
}
protected override void OnInitialized()
{
// If static rendered, do not start the timer
if (httpContext is not null)
return;
timer = new Timer();
timer.Interval = 1000;
timer.Elapsed += OnTimerInterval;
timer.AutoReset = true;
// Start the timer
timer.Enabled = true;
}
private Timer? timer;
private async void OnTimerInterval(object? sender, ElapsedEventArgs e)
{
IncrementCount();
await InvokeAsync(StateHasChanged);
}
public void Dispose()
{
timer?.Dispose();
}
}
Note that in Net9, you can simply do this to detect the RenderMode.
if (RendererInfo.IsInteractive)
Note: Interactivity Location:
I'm also not an expert but what I can see there is missing [parameter] by declaration of variable currentCount. It should be [parameter] private int currentCount = 0;
@implements IDisposable
– Henk Holterman Commented Jan 2 at 13:51