aspnetcore: All component parameters setters invoked when any component event is triggered.
Describe the bug
I have a component. The component has a parameter. The component has any event bound.
When the event is triggered the event callback should be invoked, nothing else. The element should not be redrawn, reinitialized, its state should not be changed. This is desired behavior.
Actually something else happens. When the event is triggered, the element is initialized and all of its setters are called with default values provided in .razor file. When the element contains user input - it is destroyed / overwritten.
See the demo: BlazorComponentEvents
To Reproduce
- Create new Blazor Server App (.NET Core 3.1).
- Create new folder
Componentsin main project directory. - Create new component (
Components\Component1.razor) like this:
<h3 @attributes="AdditionalAttributes" >Component1</h3>
@code {
[Parameter] public object Tag { get; set; }
[Parameter(CaptureUnmatchedValues = true)] public IReadOnlyDictionary<string, object> AdditionalAttributes { get; set; }
}
- Add the namespace to the _Imports.razor file:
@using System.Net.Http
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.JSInterop
@using BlazorComponentEvents.Shared
@using BlazorComponentEvents.Components
- Place the component on the Index.razor page:
@page "/"
<Component1 Tag="0" @onclick="Void" />
@code {
void Void() {
;
}
}
- Set the break point on Tag parameter setter in Component1.razor file.
- Run the project.
- Observe the Tag setter is called TWICE (why? it should be called once!)
- Click on the Component1 component in the browser.
- Observe Tag setter is called again without any reason.
Further technical details
PS C:\Users\Adam> dotnet --info .NET Core SDK (reflecting any global.json): Version: 3.1.100 Commit: cd82f021f4
Runtime Environment: OS Name: Windows OS Version: 10.0.18363 OS Platform: Windows RID: win10-x64 Base Path: C:\Program Files\dotnet\sdk\3.1.100\
Host (useful for support): Version: 3.1.0 Commit: 65f04fb6db
.NET Core SDKs installed: 2.1.802 [C:\Program Files\dotnet\sdk] 2.2.402 [C:\Program Files\dotnet\sdk] 3.0.100 [C:\Program Files\dotnet\sdk] 3.1.100 [C:\Program Files\dotnet\sdk]
.NET Core runtimes installed: Microsoft.AspNetCore.All 2.1.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.1.14 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.2.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.App 2.1.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.1.14 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.2.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 3.0.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 3.1.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.NETCore.App 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.1.14 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.2.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 3.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 3.1.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.WindowsDesktop.App 3.0.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 3.1.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Comments: 27 (2 by maintainers)
This is my last message in this thread, because I think it is time to close it.
@HTD I think your reaction is too emotional. Nobody has questioned your competency. People take their free time to explain how Blazor works. You have got a few helpful tips with links to documentation and other issues. Issues on Github are primarily used to report bugs. You already know that this is not a bug. What else can we do for you?
I get it, but please read your other posts where you have written:
Maybe after 30 years of programming I don’t have enough experience yet, but as far as I know there are 3 primary UI component concepts:
Update()function on behalf of every component.Blazor is number 3. Blazor is in production and nobody will change this fundamental behaviour.
Take into account that in a complex component you can use parameter values and data from injected services during rendering. Don’t forget about
CascadingParameter. Framework authors don’t know in advance how your component works. The only universal way to check if something has changed is to keep a copy and compare all values after each event. It is not efficient.In real application usually you want to render components automatically and that is default behaviour in Blazor. If you think it is a performance problem you can always override
ShouldRendermethod as mentioned by @enetstudio above or/and useActioninstead ofEventCallbackwhere it makes sense.You already know that you should not use parameters to keep component’s state - use private variables/properties or keep state in injected service. This was demonstrated in my answer here https://github.com/dotnet/aspnetcore/issues/18279#issuecomment-574776872.
If you want to keep state in parameters you can always implement two way data binding as described here: https://docs.microsoft.com/en-us/aspnet/core/blazor/components?view=aspnetcore-3.1#data-binding
You have plenty of options - choice is yours.
@HTD In my humble opinion there is no bug in Blazor - it is simply designed to rerender component after each event. It doesn’t matter what you do in your event handler code. Your example with empty
Void()method is not a real life example. As I said in my previous posts people handle events because they want to do something. You can handle mouse movements to display pointer coordinates or highlight elements under pointer. You will be more than happy when you notice that it simply just works and you don’t have to callStateHasChangedin your every method and you don’t have to implement data models with a lot of boilerplate code to implement notification events for every property.Your example:
doesn’t work because there is probably no support in Blazor for
datetime-local. It is also not supported in at least a few browsers. You can find this in the doc:Maybe Blazor doc is not perfect yet but it is a valuable source of information. Read it two times from the beginning to the and and you will see that Blazor is fantastic!
I know using
Actioninstead ofEventCallbackworks well. The one particular bug report concernsEventCallbacktrigger re-rendering of the component, which is incorrect and undesired behavior.This is nothing right about re-rendering just because mouse event was bound to an empty user code. I already found an ugly hack to workaround this issue, however it would be so infinitely better if we just could use Blazor without resort to ugly hacks and going through unexpected behaviors.
I know doing browser based UI work seamlessly with .NET Core is a really hard problem, but something tells me that handling the events correctly is not impossible. Triggering the event does not redraw elements in web browser, it should not redraw elements in Blazor. As simple as that. Triggering one-way binding should at least consider redrawing, but not triggering just the event handler.
BTW, the incorrect behavior is not limited to HTML element based events. It’s related to the
EventCallbackusage. The problem doesn’t occur whenActionis used instead ofEventCallback. But AFAIR,EventCallbackwas made to replaceActionusage for event handling in Blazor. As such, it should behave properly, without highly undesired side effects.Let me be absolutely clear about what happens here, I will use a car example. I have a car with an alarm system. I open my car with a remote and this triggers the alarm. This is a bug, not a feature. I want just to open my car without triggering the alarm. Please don’t tell me triggering the alarm each time I enter the car is necessary. It isn’t. Also, don’t instruct me to use mechanical lock instead the remote control, because the role of the car remote is not to trigger the alarm, but to open the doors. The role of the event handler is to notify user code that something happen, without drawing anything in the UI. Drawing, redrawing, re-rendering is like setting the car alarm when opening the doors. The doors are open, but the alarm should not be triggered.
You can try to use
Actioninstead ofEventCallback. This will not callStateHasChangedautomatically in the parent component.