Aspnetcore: MouseEvent causes re-render on every click?

Created on 20 Dec 2019  路  7Comments  路  Source: dotnet/aspnetcore

Describe the bug

Clicking on a component causes a re-render of that component.

Example, if I click anywhere within a component on a page, my debug log begins with this:

[15:47:36 0HLS5HMITRJSV:00000001 DBG] <Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher> Received hub invocation: InvocationMessage { InvocationId: "", Target: "DispatchBrowserEvent", Arguments: [ {"browserRendererId":0,"eventHandlerId":12,"eventArgsType":"mouse","eventFieldInfo":null}, {"type":"click","detail":1,"screenX":2343,"screenY":281,"clientX":423,"clientY":178,"button":0,"buttons":0,"ctrlKey":false,"shiftKey":false,"altKey":false,"metaKey":false} ], StreamIds: [  ] }.
[15:47:36 0HLS5HMITRJSV:00000001 DBG] <Microsoft.AspNetCore.Components.RenderTree.Renderer> Handling event {"Id": 5, "Name": "HandlingEvent"} of type 'MouseEventArgs'
[15:50:50 0HLS5HOV76P1D:00000001 DBG] <Microsoft.AspNetCore.Components.RenderTree.Renderer> Rendering component 16 of type GridBlazor.Pages.GridComponent`1[Model.EventNotification]
[15:50:50 0HLS5HOV76P1D:00000001 DBG] <Microsoft.AspNetCore.Components.RenderTree.Renderer> Rendering component 17 of type Microsoft.AspNetCore.Components.CascadingValue`1[GridBlazor.Pages.GridComponent`1[Model.EventNotification]]
[15:50:50 0HLS5HOV76P1D:00000001 DBG] <Microsoft.AspNetCore.Components.RenderTree.Renderer> Rendering component 29 of type GridBlazor.Pages.GridExtSortHeaderComponent`1[Model.EventNotification]
[15:50:50 0HLS5HOV76P1D:00000001 DBG] <Microsoft.AspNetCore.Components.RenderTree.Renderer> Rendering component 18 of type Microsoft.AspNetCore.Components.CascadingValue`1[GridBlazor.Pages.GridComponent`1[Model.EventNotification]]
[15:50:50 0HLS5HOV76P1D:00000001 DBG] <Microsoft.AspNetCore.Components.RenderTree.Renderer> Rendering component 30 of type GridBlazor.Pages.GridHeaderComponent`1[Model.EventNotification]
[15:50:50 0HLS5HOV76P1D:00000001 DBG] <Microsoft.AspNetCore.Components.RenderTree.Renderer> Rendering component 19 of type Microsoft.AspNetCore.Components.CascadingValue`1[GridBlazor.Pages.GridComponent`1[Model.EventNotification]]
[15:50:50 0HLS5HOV76P1D:00000001 DBG] <Microsoft.AspNetCore.Components.RenderTree.Renderer> Rendering component 31 of type GridBlazor.Pages.GridHeaderComponent`1[Model.EventNotification]
[15:50:50 0HLS5HOV76P1D:00000001 DBG] <Microsoft.AspNetCore.Components.RenderTree.Renderer> Rendering component 20 of type Microsoft.AspNetCore.Components.CascadingValue`1[GridBlazor.Pages.GridComponent`1[Model.EventNotification]]
[15:50:50 0HLS5HOV76P1D:00000001 DBG] <Microsoft.AspNetCore.Components.RenderTree.Renderer> Rendering component 32 of type GridBlazor.Pages.GridHeaderComponent`1[Model.EventNotification]
[15:50:50 0HLS5HOV76P1D:00000001 DBG] <Microsoft.AspNetCore.Components.RenderTree.Renderer> Rendering component 21 of type Microsoft.AspNetCore.Components.CascadingValue`1[GridBlazor.Pages.GridComponent`1[Model.EventNotification]]
[15:50:50 0HLS5HOV76P1D:00000001 DBG] <Microsoft.AspNetCore.Components.RenderTree.Renderer> Rendering component 33 of type GridBlazor.Pages.GridHeaderComponent`1[Model.EventNotification]
[15:50:50 0HLS5HOV76P1D:00000001 DBG] <Microsoft.AspNetCore.Components.RenderTree.Renderer> Rendering component 22 of type Microsoft.AspNetCore.Components.CascadingValue`1[GridBlazor.Pages.GridComponent`1[Model.EventNotification]]
[15:50:50 0HLS5HOV76P1D:00000001 DBG] <Microsoft.AspNetCore.Components.RenderTree.Renderer> Rendering component 34 of type GridBlazor.Pages.GridHeaderComponent`1[Model.EventNotification]
[15:50:50 0HLS5HOV76P1D:00000001 DBG] <Microsoft.AspNetCore.Components.RenderTree.Renderer> Rendering component 23 of type Microsoft.AspNetCore.Components.CascadingValue`1[GridBlazor.Pages.GridComponent`1[Model.EventNotification]]
[15:50:50 0HLS5HOV76P1D:00000001 DBG] <Microsoft.AspNetCore.Components.RenderTree.Renderer> Rendering component 35 of type GridBlazor.Pages.GridHeaderComponent`1[Model.EventNotification]
[15:50:50 0HLS5HOV76P1D:00000001 DBG] <Microsoft.AspNetCore.Components.RenderTree.Renderer> Rendering component 24 of type Microsoft.AspNetCore.Components.CascadingValue`1[GridBlazor.Pages.GridComponent`1[Model.EventNotification]]
[15:50:50 0HLS5HOV76P1D:00000001 DBG] <Microsoft.AspNetCore.Components.RenderTree.Renderer> Rendering component 36 of type GridBlazor.Pages.GridHeaderComponent`1[Model.EventNotification]

...

And then after that it re-renders that component. For small components this isnt a problem, but for others that are for example, grids, it becomes a performance issue where simply clicking within the grid or any part of it will cause a complete re-render of the _entire_ grid.

Is this intended?

Edit:

I realize I can override ShouldRender, but right now ShouldRender doesn't contain any information as to where the call came from or how the request to render was raised. It would be handy, for example, if ShouldRender had some sort of parameter that would say "Hey, I am asking if I should render because of a MouseClickEvent or a DragEvent etc. Is this out-of-scope for what the team is envisioning for core and should the developer instead implement this on their own end?

Further technical details

  • ASP.NET Core version : 3.1
  • Include the output of dotnet --info
.NET Core SDK (reflecting any global.json):
 Version:   3.1.100
 Commit:    cd82f021f4

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.17763
 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.700 [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.11 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.1.14 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.App 2.1.11 [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 3.0.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 3.0.1 [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.11 [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 3.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 3.0.1 [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.0.1 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 3.1.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

To install additional .NET Core runtimes or SDKs:
  https://aka.ms/dotnet-download

  • Visual Studio 2019 16.3
Author Feedback area-blazor

Most helpful comment

The @onclick should be on the thing you click to filter, not on the grid. If you make that filter UI a component too, the re-render should be on the component clicked + all components inside of it, not parent components. So just the filter, and not the grid.

Your scenario sounds very bad though. What is your real scenario?

All 7 comments

ShouldRender doesn't need to know what caused the render, it only needs to know if any state has changed that should result in a change to the display.

@Oninaig does @mrpmorris 's suggestion help? Could you elaborate why the event source is important to make a rendering decision?

@mrpmorris @pranavkm I understand the theory behind "ShouldRender" not needing to know what caused it to render, but rather it only needs to know if any state has changed that "should" result in a change to the display.

I think my need is better described as something along the lines of the following "pipeline":

Preamble: Lets say you are dealing with an arbitrary component with the following parameters:

1.) Our component is a grid of 100 items.
2.) Lets also say for the sake of argument that we aren't using paging and we cannot use paging (just humor me here).
3.) Lets also assume that whatever service that retrieves the items takes approximately 2-5 seconds to retrieve new items.
4.) Finally, our grid itself is bound to the service, such that any re-render of the grid causes the service to retrieve the items again in the event that there are new items to display.

This means that whenever the user clicks _anywhere_ on the grid, the service retrieves new items. If the user just clicks on a filter button (order by descending, for example) I can do that instantly. Boom, the items are now descending. This takes less than 1ms. So now the items are sorted in descending order, but the service has been fired because of the click event and now 2-5 seconds later, the "new" items return and it refreshes the grid which is very jarring for the user.

Essentially they had been scrolling down their grid of sorted items when suddenly the items change because the service has returned a new collection (in our case, of the same items but since it is bound to the grid, it replaces the existing items with the _same_ items but not sorted anymore like the user requested until a moment later when the newly returned list takes on the filter the user selected and again, another jarring change occurs on the grid.

So, we have the following pipeline:

1.) User clicks something on the component which causes asp core to fire an event (which is fine, it does this for all events so that if anything _does_ need to be re-rendered it can do so.
2.) ShouldRender is called for our arbitrary component. In my case I want to say "Oh did this request for ShouldRender come from a click event on the filter button? In that case, don't re-render, just process the filter, etc.

Now that I think about it more, are you saying its a better idea to instead bind to the click action of the filter itself and set some sort of flag saying something along the lines of:

FilterClicked = true;

And then, because the click event on the filter is fired _first_ before ShouldRender, I can override ShouldRender and check if FilterClicked == true; and in that case return false?

The @onclick should be on the thing you click to filter, not on the grid. If you make that filter UI a component too, the re-render should be on the component clicked + all components inside of it, not parent components. So just the filter, and not the grid.

Your scenario sounds very bad though. What is your real scenario?

This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for 4 days. It will be closed if no further activity occurs within 3 days of this comment. If it is closed, feel free to comment when you are able to provide the additional information and we will re-investigate.

See our Issue Management Policies for more information.

@mrpmorris your suggestion definitely helps and I have reduced the redundant re-renders but a bunch. Thank you so much.

@mrpmorris your suggestion definitely helps and I have reduced the redundant re-renders but a bunch. Thank you so much.

I appreciate you letting me know. Thank you!

Was this page helpful?
0 / 5 - 0 ratings