'Why do i need to call StateHasChanged if the method contains an await call
Look at this code (blazor server page):
@page "/"
<div>@str</div>
<button @onclick="eventArgs => { OnClick(); }">Hello</button>
@code
{
private string str { get; set; }
private void OnClick()
{
str = "Hello world";
}
}
This code works perfectly. I can see "Hello world" on my page when i click on the button.
Now look at this code:
@page "/"
<div>@str</div>
<button @onclick="eventArgs => { OnClick(); }">Hello</button>
@code
{
private string str { get; set; }
private async Task OnClick()
{
await ReadDataBase();
str = "Hello world";
StateHasChanged();
}
}
I do not understand why but the str is not refreshed immediately if i do not put StateHasChanged(). My question is ... why ?
Thanks
Solution 1:[1]
I do not understand why but the str is not refreshed immediately if i do not put StateHasChanged(). My question is ... why ?
This is partially true. The str variable is assigned with the string "Hello world", and the page is re-rendered only after the call to ReadDataBase has returned. Note that the call to OnClick should be awaited, like this:
`@onclick="async eventArgs => { await OnClick(); }"`
But this is not why "the str is not refreshed immediately." The reason for that is the call to the ReadDataBase method before you assing a value to str... when you await the ReadDataBase method , execution is yielded to the calling code, and the framework performs other tasks... Only after the ReadDataBase method has returned (when the task completes), the code in the OnClick method continues execution of the next line which is the assignment of the string "Hello world" to str, and only then the component is re-rendered, if you add a call to StateHasChanged method or still better do @onclick="async eventArgs => { await OnClick(); }" and remove the call to StateHasChanged
Conclusion: If you want to assign a value to str and refresh the display immediately, place str = "Hello world"; in the first line, and await ReadDataBase(); in the second line. It will work without calling StateHasChanged, and even if you use
<button @onclick="eventArgs => { OnClick(); }">Hello</button>
instead of
@onclick="async eventArgs => { await OnClick(); }"
Incidentally, I'd do something like this:
<button @onclick="eventArgs => OnClick()">Hello</button>
Now try to understand what's the difference between "eventArgs => OnClick()" and "eventArgs => { OnClick(); }"
Try this code:
<div>@str</div>
<button @onclick="OnClick">Click me</button>
@code
{
private string str { get; set; }
private async Task OnClick()
{
str = "Hello world";
// Simulate an async call to ReadDataBase
await Task.Delay(3000);
}
}
Update and clarification:
I guess you know when not to call the StateHasChanged method manually, hence your question...Indeed, in the old days, one had to call this method manually, after the component's state has changed, in order to inform the component that its state had changed, and that it should re-render. Later on, the call to the StateHasChanged method manually was rendered superflous, when UI events, such as 'click' are involved. The reasoning behind it was that UI events are almost always result in the modification of the component's state, so let us save the developer the burden of adding it manually. Now, as your OnClick method is an event handler, adding the StateHasChanged method manually is not necessary, and yet without adding it manually the component is not refreshed (re-render). The reason for that behavior is that you call OnClick syncronously, without returning a Task object that should tell Blazor that the OnClick method has completed. In such a case, Blazor has no way to know when the OnClick method has completed, and thus, it does not automatically call the StateHasChanged method. But when you add the StateHasChanged method manually, the component is refreshed (re-rendered). Of course, you may not add it manually, but call the OnClick method asyncronously.
Solution 2:[2]
The reason is you are not awaiting your async method inside your lambda. In other words, change your code to this:
<button @onclick="async eventArgs => { await OnClick(); }">Hello</button>
Having said that, if all you are doing is calling an async method and never actually use the awaited task, you can remove the brackets { } and change your code to the following:
<button @onclick="() => OnClick()">Hello</button>
If you do that, the framework will wrap you lambda in a Func<Task> for you.
Now, I am assuming you used a lambda for a reason, such as you want to pass some arguments to it. If this is not the case you simply could make your code like the following also:
<button @onclick="OnClick">Hello</button>
When you do not await on an async method, execution continues without waiting for your task to complete. Basically, here is what happens in your code:
- Click the button
- ReadDataBase called
- StateHasChanged called by framework
strnot updated yet, so UI does not refreshstrfinally gets updated- You need to call
StateHasChangedmanually to update the UI
Solution 3:[3]
What your version is missing is a return, this will work:
<button @onclick="eventArgs => { return OnClick(); }">Hello</button>
which you can shorten to:
<button @onclick="eventArgs => OnClick()">Hello</button>
and when there are no parameters you can simplify that to just
<button @onclick="OnClick">Hello</button>
In all three cases the method assigned to @onclick now returns a Task and will be awaited. And then you don't need the StatehasChanged(); line anymore.
The generated code for @onclick= is adapted to accepting a Task method and (not) using eventArgs automatically.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | |
| Solution 3 |
