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

  1. Create new Blazor Server App (.NET Core 3.1).
  2. Create new folder Components in main project directory.
  3. 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; }
}
  1. 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
  1. Place the component on the Index.razor page:
@page "/"
<Component1 Tag="0" @onclick="Void" />
@code  {
    void Void() {
        ;
    }
}
  1. Set the break point on Tag parameter setter in Component1.razor file.
  2. Run the project.
  3. Observe the Tag setter is called TWICE (why? it should be called once!)
  4. Click on the Component1 component in the browser.
  5. 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)

Most upvoted comments

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?

Void function doing nothing - seriously, you don’t get a minimal example?

I get it, but please read your other posts where you have written:

It gets re-rendered, however nothing has changed in the component.

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:

  1. Components without any automatic updates. Each time you want to update information on the screen you have to call Update() function on behalf of every component.
  2. Components similar WPF where MVVM pattern is implemented and you have to create data models with property change notifications.
  3. Components which render itself after every event.

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 ShouldRender method as mentioned by @enetstudio above or/and use Action instead of EventCallback where 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 call StateHasChanged in 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:

<input @bind="DateTime" type="datetime-local" />

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:

When a user provides an unparsable value to a databound element, the unparsable value is automatically reverted to its previous value when the bind event is triggered.

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 Action instead of EventCallback works well. The one particular bug report concerns EventCallback trigger 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 EventCallback usage. The problem doesn’t occur when Action is used instead of EventCallback. But AFAIR, EventCallback was made to replace Action usage 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 Action instead of EventCallback. This will not call StateHasChanged automatically in the parent component.