blazor-state icon indicating copy to clipboard operation
blazor-state copied to clipboard

How to avoid a mass rerendering of everything that depends on only unchanged parts of a State object?

Open szalapski opened this issue 3 years ago • 13 comments

Anytime a handler mutates the state, any component that gets state properties and inherits from BlazorStateComponent will automatically rerender. Right?

So that means that any component that depends on one part of that state but not another part will still rerender regardless of what part of the state they need. In other words, if my component shows MyState.A but not MyState.B, and there is a change to MyState.B, my component will rerender even though it doesn't need to.

Suppose I enable a rather complex domain object to be edited piecemeal. So the user might edit a CommentsPanel that internally uses CurrentOrderState.OrderComments and a RushShippingPanel that internally uses CurrentOrderState.IsRushShipping, along with dozens of other properties. So now any change anywhere in the state will result in dozens of components being rerendered when it is more desirable that only one part at a time needs to be rerendered. Is there any way to get more granular with rerendering of components that inherit from BlazorStateComponent? It strikes me that rerendering everything that depends on a state object when anything in that state object changes (because it's a whole new state each time) is a very blunt approach.

I am thinking there should be a way to avoid rerendering for unchanged parts of the state, as it is inevitable that even the simplest properties on a state object will themselves have properties, many of which won't change.

Side note: I am using the term "rerender" for the process by which .NET (in WebAssembly) runs the C# code to figure out the overall presentation state of each component in memory. After that, OnAfterRender is called, then manipulating the DOM is a subsequent process. I try to avoid using the term "render" to mean "manipulating the DOM", since it seems Microsoft uses it to mean running the C# code.

For pages of significant scope, rerendering can be the source of much slowness. If I am showing a 30x10 cell table, and each cell has 1-3 components in it, it can be quite burdensome to rerender (run the C# code to determine what the DOM should become) hundreds of components. So it would be nice if there was a way to avoid rerendering a component if the parts of the state it depends on have not changed. I have written a small package to do this apart from state management, but I was hoping that changes due to state management wouldn't need such a thing (nor do I see a way to make them collaborate together).

Vue and React seem to automagically handle this (I think internally they compare previous state to new state on a per-property basis), but not so with Blazor (hence the need for StateHasChanged at all).

szalapski avatar Nov 01 '21 14:11 szalapski

FYI, I am also exploring the same concern with the Fluxor library.

szalapski avatar Nov 01 '21 14:11 szalapski

Currently the subscription is generically placed at the state level. So we break up States into smaller pieces but yes a complex object that is all in one state would re-render all. Now we can fix the rendering problem by making the subscription more specific to the property. But doing so also removes the simplicity of automatic subscriptions.

I have not yet run into the scenario where I have found that required. If you have something you could share as a decent test case that would be awesome.

StevenTCramer avatar Nov 02 '21 16:11 StevenTCramer

Suppose I have a single-page app that might want state management to store the possible filter values for each of serveral fields in an advanced search, and the current value of each field, and the current search results, and the currently selected item in the search results, and whether that item's detail is currently being shown. One component might show several of these pieces and another compnoent will show some of them as well as others. I think you would suggest I break up this into several state classes, and I agree...but it would be quite nice to have a single master state object where I can use Intellisense to find what I want in the state, so I can just reference what I want, e.g. getting State.Filters.DateRange.AllValues or State.SearchResults.First() or State.SearchResults[42].IsSelected or State.CurrentItem.IsOpen.

Now for example, say I have an action that toggles a boolean on one of the search results, (State.SearchResults[42].IsSelected) and the only thing in the whole state that renders differently based on that boolean value is one component in the several dozen used to show that component.

Ideally, any change to the state would not cause a rerender of thousands of components I am showing nor even the dozens for this item, but only the one that actually shows a check mark or a frowny face for State.SearchResults[42].IsSelected's value. Ideally, I want to specify it at any level down the tree of objects in the state. so that other components used to show the items in the collection don't rerender.

szalapski avatar Nov 08 '21 21:11 szalapski

This can be done. But is not trivial. I will ad it to my back log.

StevenTCramer avatar Nov 13 '21 10:11 StevenTCramer

Great...I want to help. In investigating your code, it isn't obvious to me how to do this.

I really want to avoid ceremony code that "registers" which parts of a state object a component depends on. Ideally, my component would just use the Getters naturally, and Blazor-State via BlazorStateComponent somehow would...

  1. Know which parts of the state have changed
  2. Know which parts of the state are referenced by the component (either in markup or in @code)
  3. Call StateHasChanged only if there is any property that is in both 1 and 2

(Enabling collection item changes too might be a further enhancement, but I'm happy to limit the scope to property sets for now)

I've thought through bullet 2 and I have some ideas, but none stand out as particularly nice. So if you have some ideas about how to do the above, especially bullet 2 above which seems like the hardest part, want to do a screen-sharing session some time? I'm interested to help on this if you'd welcome it.

szalapski avatar Nov 16 '21 17:11 szalapski

Screen sharing session is a good idea, Please join the discord server and lets discuss more. https://discord.gg/A55JARGKKP

StevenTCramer avatar Nov 17 '21 03:11 StevenTCramer

As discussed on Discord, here's a minimal demo of the rerendering issue #266 that I would like to help make nuanced: https://szalapski.github.io/BlazorRerenderReducers/blazor-state-demo And here's the code: https://github.com/szalapski/BlazorRerenderReducers/tree/main/Sz.BlazorRerenderReducers/Client/BlazorStateDemo

szalapski avatar Nov 29 '21 15:11 szalapski

I have not forgotten about this. Need to get to it.

StevenTCramer avatar Jul 01 '22 16:07 StevenTCramer

Want to set a date to look into it together? Contact me in a private message, via linkedin perhaps.

On Fri, Jul 1, 2022, 5:09 PM Steven T. Cramer @.***> wrote:

I have not forgotten about this. Need to get to it.

— Reply to this email directly, view it on GitHub https://github.com/TimeWarpEngineering/blazor-state/issues/266#issuecomment-1172497932, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAUY5MLOPGBSMYXRHE3GVZ3VR4J3BANCNFSM5HEGVZ7Q . You are receiving this because you authored the thread.Message ID: @.***>

szalapski avatar Jul 01 '22 20:07 szalapski

React classes handle these cases with ShouldComponentUpdate returning a boolean. Perhaps something like that could be added to the base component and called here before firing rerendering

zewa666 avatar Sep 10 '23 10:09 zewa666

The only workaround I've had for this is to try and separate my stores as much as I can and copy data that's shared between them only when that data updates.

I think a ShouldUpdate would be really helpful since Blazor doesn't have it OOTB (at least not how it is in React). I bet CloneState could be utilized to pass the original and changed state.

We could possibly automate that (maybe in an opt-in fashion) by subscribing to individual store properties rather than the store a a whole? Whatever props are subscribed to could be automatically compared in a base ShouldUpdate method.

petersondrew avatar Sep 10 '23 12:09 petersondrew

well coming from rxjs you could pluck the values using the dot notation state.person.address.street to only get a slice of the state back. with rxjs and same with rx a distinctUntilChanged would achieve the partial slice change detection. I guess something similar, 2nd param to subscribe, could be realized with this lib as well

zewa666 avatar Sep 10 '23 16:09 zewa666