Aspnetcore: StateHasChanged() needs to be called on every component

Created on 28 Mar 2018  路  5Comments  路  Source: dotnet/aspnetcore

I setup a handler like so in the main top level component.

@functions
{
    protected override void OnInit()
    {
        state.OnChange += StateHasChanged;
    }
}

However I had to add this code to every component that cared about state, which is most components.
I would have thought being at the top level that it would have rerendered and diffed the whole tree but it only seems to do component by component (which is obviously great for performance).

In which case, the component should really subscribe to state it is interested in.

area-blazor

Most helpful comment

In which case, the component should really subscribe to state it is interested in.

Exactly - that is the expected design. It makes sense for your state class to expose different events corresponding to different state changes, and then your presentation components can subscribe to the ones they care about. This gives you whatever update granularity you require.

I hope that in the near future there will be .NET state container frameworks that make this simpler to achieve, including some kind of hierarchy of state where you could subscribe at a given level and receive notifications when changes are posted at any child level.

Right now if you really want to have a single state change event, and have all your components re-render when it changes, you could create a custom base class for all your components whose Init method adds the event handler. Remember to implement IDisposable and unregister the event handler on disposal, otherwise you'll be leaking memory as the set of components changes over time.

All 5 comments

In which case, the component should really subscribe to state it is interested in.

Exactly - that is the expected design. It makes sense for your state class to expose different events corresponding to different state changes, and then your presentation components can subscribe to the ones they care about. This gives you whatever update granularity you require.

I hope that in the near future there will be .NET state container frameworks that make this simpler to achieve, including some kind of hierarchy of state where you could subscribe at a given level and receive notifications when changes are posted at any child level.

Right now if you really want to have a single state change event, and have all your components re-render when it changes, you could create a custom base class for all your components whose Init method adds the event handler. Remember to implement IDisposable and unregister the event handler on disposal, otherwise you'll be leaking memory as the set of components changes over time.

Where should I remove the subscription to the event though?

I have implemented IDisposable on a component, and Disposed is not called when I close the browser tab.

Implementing IDisposable on a Transient service, I cannot get Disposed to be called at all.

I have impemented IDisposable on a Scoped service, and Disposed is not called either, when I close the tab.

I have no idea where and when I am supposed to clean up resources.

@FrikkinLazer You might be hitting https://github.com/aspnet/AspNetCore/issues/9893

That definitely looks like part of the problem. There seems to be a lot of work left before Blazor serverside code can be written that guarantees that no memory leaks occur.

I have a question about this StateHasChanged() on components though. Lets say that I want to create a simple reusable control. Since every control needs to be aware of all the places where every value it binds to can change, so that it can call StateHasChanged(), how can components be made truly reusable? Every time you use it somewhere, you will have to change the controls code, to subscribe to some event somewhere, otherwise it will not update. I am propably missing something.

EDIT: I also want to add that it seems very confusing that you have to call a method called StateHasChanged on something, when the state of the thing you are calling it on has not changed, only the state of some other object it is bound to.

@FrikkinLazer Yeah, server-side Blazor is still a work in progress. One of our major focuses right now is getting server-side Blazor ready for production usage. The team is shifting focus from feature work to fundamentals like stress, perf, security, etc.

The StateHasChanged() method is the signal used to indicate that some state the component's rendering logic depends on has changed and the component should be rerendered. Typically the component's state is completely under it's own control. If you need to pass state into a component you do that with component parameters. if the component subscribes to some event that it uses to then update its state, then that's a common case where you need to call StateHasChanged() manually.

We have quite a few folks building reusable components for Blazor today (e.g. Telerik, DevExpress, Syncfusion. Some of these components are pretty elaborate.

I'd be interested to know what are the cases where you are finding this model unusable. Please open an issue on the https://github.com/aspnet/aspnetcore repo describing the component you're trying to implement and we'll see if we can help you out.

Was this page helpful?
0 / 5 - 0 ratings