I'm making a Blazor Server Side project and I wanted to make a button that desactivate after a click, but without using the disabled
attribute of <button>
. The code is pretty simple :
@functions {
LogInForm logInForm = new LogInForm();
bool IsDisabled;
SignInResult result;
protected override void OnInitialized()
{
IsDisabled = false;
}
async Task TryLogIn()
{
IsDisabled = true;
StateHasChanged();
result = await _LogInService.TryLogIn(logInForm);
Console.WriteLine("Logging status : " + (result.Succeeded ? "Sucess" : "Failure"));
IsDisabled = false;
StateHasChanged();
}
}
For odd reasons, the first StateHasChanged
isn't triggering but the second does re-render the page. It can be quite easily testes by going in Debug mode and entering into the StateHasChanged()
method. On second time call it does stop on the HTML code after going into the method but not the first time.
Why so ?
NB : I'm not looking for any workaround using Task.Delay
or Task.Run(...)
only, as it exist a race condition between those threads and the UI refresh thread, and so it is not a reliable solution. I'm looking for answers on the StateHasChanged()
behaviour or a workaround by using events like PropertyChanged
or EventCallback
and putting the button as a child component.
Edit : After some testing, it seems that StateHasChanged()
only triggers the re-render of the component after an await
operation on a Task
. It can be easily tested by putting in commentary the result = await _LogInService.TryLogIn(logInForm);
line or changing IsDisabled = ...
to await new Task.Run(() => { IsDisabled = ...})
. I have some workaround now, but I still wonder why this is a feature. Shouldn't StateHasChanged()
re-render after any operations ? Or it consider that only async
operations (so mostly server-calls) can change something in the UI ?
The Blazor team is about to publish documentation about how StateHasChanged() works.
You can track it here here: https://github.com/aspnet/AspNetCore/issues/14591
For the time being, I think this explanation taken from a github comment is an excellent explanation:
Adding a call to StateHasChanged simply queues the component to be rendered. The renderer decides when the renders happen.
This can be triggered by 4 circumstances:
- Initial render where the bootstrap process triggers the initial render of the root component and all its children.
- An event, in which the component that handles the event automatically triggers a new render after the event, and potentially its children if it renders new children or change their parameters.
- As a result of calling StateHasChanged from an InvokeAsync call (marshalling back into the UI thread, essentially)
- As a result of the parent component changing the parameters for the child component, which happens as part of the diffing process when the renderer calls SetParametersAsync on the child component.
To be very clear, calling StateHasChanged only queues a Render for the component or "marks it as dirty".
It's the renderer the one that decides when and how to produce the renders. BuildRenderTree does not result in new rendered output, only in a new definition of the "V-DOM" for the component at the time it's being called.
Normally, a component gets rendered once per render batch (which is a collection of components that are rendered/diffed together and sent to the UI for update). There are only two situations in which a component renders more than once in a batch:
- You have a component that directly implements IComponent and calls RenderHandle.Render
- You have a circular dependency between a child and a parent component that might cause a parent to re-render as part of a children invoking some callback parameter from the parent as part of its initialization
Source: https://github.com/aspnet/AspNetCore/issues/15175#issuecomment-544890549
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With