Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When should I call StateHasChanged and when Blazor automatically intercepts that something is changed?

Tags:

blazor

I am having a hard time understanding when I should call StateHasChanged() and when Blazor intercepts that something is changed so it must be re-rendered.

I've created a sample project with a button and a custom component called AddItem. This component contains a div with a red border and a button.

What I expected: I want that the AddItem's div will show up when the user clicks on the button contained inside the Index page. Then I want to hides it when the user clicks on AddItem's button.

Note: AddItem doesn't expose it _isVisible flag outside, instead it contains a Show() method. So AddItems.Show() will be invoked when the Index's button is clicked.

Tests:

  1. I click on Index's click button then the methods Open() and AddItem.Show() are invoked. The flag _isVisible is set to true but nothing happens and Index's ShouldRender() is invoked.

    Console output:

    • Render Index
  2. I've modified AddItem.Show() with public void Show() {_isVisible = true; StateHasChanged();}. Now the AddItem's div shows and hide as expected.

    Console output:

    • Render AddItem (1° click on index's button)
    • Render Index (1° click on index's button)
    • Render AddItem (2° click on addItem's close button)
  3. I've modified <AddItem @ref="AddItem" /> with <AddItem @ref="AddItem" CloseEventCallback="CallBack" />, removed StateHasChanged from AddItem's Show() method. Now the AddItem's div shows and hides as expected.

Based on Test 3: Why I don't have to explicit StateHasChanged if I set AddItem's CloseEventCallback to any parent's method? I'm having a hard time understanding it because AddItem doesn't invoke CloseEventCallback anywhere.

and

When Blazor understands that something is changed so it must be re-render?

My sample code (if you want to try it).

My Index.razor

<AddItem @ref="AddItem" /> <button @onclick="Open">click</button> @code {     AddItem AddItem;      public void Open()     {         AddItem.Show();     }      public void CallBack()     {     }      protected override bool ShouldRender()     {         Console.WriteLine("Render INDEX");         return base.ShouldRender();     } } 

My AddItem component

@if (_visible) {     <div style="width: 100px; height: 100px; border: 1px solid red">testo</div>     <button @onclick="Close">close</button>     }  @code {     private bool _visible = false;      [Parameter] public EventCallback<bool> CloseEventCallback { get; set; }      public void Show()     {         _visible = true;     }      public void Close()     {         _visible = false;     }      protected override bool ShouldRender()     {         Console.WriteLine("Render ADDITEM");         return base.ShouldRender();     } } 
like image 621
Leonardo Lurci Avatar asked Feb 06 '20 13:02

Leonardo Lurci


People also ask

What does StateHasChanged do Blazor?

StateHasChanged notifies the component that its state has changed. When applicable, calling StateHasChanged causes the component to be rerendered. StateHasChanged is called automatically for EventCallback methods. For more information on event callbacks, see ASP.NET Core Blazor event handling.

What is EventCallback Blazor?

The EventCallback<T> class is a special Blazor class that can be exposed as a Parameter so that components can easily notify consumers when something of interest has occurred.

How do you Rerender a component in Blazor?

To force a component to rerender, use the “StateHasChanged” method in Blazor, to notify that the state has been changed and requires re-rendering.

Does Blazor use INotifyPropertyChanged?

Blazor. Reactivity, you can take advantage of INotifyPropertyChanged and INotifyCollectionChanged to create reactive components. The library will take care of watching changes for you.


1 Answers

Generally speaking, the StateHasChanged() method is automatically called after a UI event is triggered, as for instance, after clicking a button element, the click event is raised, and the StateHasChanged() method is automatically called to notify the component that its state has changed and it should re-render.

When the Index component is initially accessed. The parent component renders first, and then the parent component renders its child.

Whenever the "Open" button is clicked the Index component re-renders (This is because the target of the event is the parent component, which by default will re-render (No need to use StateHasChanged). But not the child, who is not aware that his state has changed. In order to make the child aware that his state has changed and that it should re-render, you should add a call to the StateHasChanged method manually in the Show method. Now, when you click on the "Open" button, the child component is re-rendered first, and then its parent re-renders next. Now the red div is rendered visible.

Click the "Close" button to hide the red div. This time only the child component re-renders (This is because the target of the event is the child component, and it re-renders by default), but not the parent.

This behavior is correct and by design.

If you remove the call to the StateHasChanged method from the AddItem.Show method, define this property: [Parameter] public EventCallback<bool> CloseEventCallback { get; set; }, and add a component attribute in the parent component to assign a value to this property like this: <AddItem @ref="AddItem" CloseEventCallback="CallBack" />, you'll notice no change outwardly, but this time the order of re-rendering when the "Open" button is clicked, is first the parent re-renders, then the child re-renders. This describes exactly the issue you've found expressed in your question from the comments:

So, why my test 3 worked as expected even if CloseEventCallback isn't invoked anywhere?

You are right... I could not really explain this behvior before having a further investigation. I'll try to find out what is going on, and let you know.

AddItem's close method invoke the CloseEventCallback to advise the parent that it should re-render.

Note: your code define the CloseEventCallback with a boolean type specifier, so you must define a method in your parent component that has a boolean parameter. When you invoke the CloseEventCallback 'delegate' you actually call the Index.Callback method and you should pass it a boolean value. Naturally, if you passes a value to a component, you expect it to re-render so that the new state can be seen in the UI. And this is the functionality that the EventCallback provides: Though the event is triggered in the child component, its target is the parent component, which results in the parent component re-rendering.

I am wondering why a parent component should re-render itself if one of the subscribed EventCallback is invoked?

This is exactly what I'm trying to explain in the paragraph above. The EventCallback type was especially design to solve the issue of the event target, routing the event to the component whose state has changed (the parent component), and re-rendering it.

like image 53
enet Avatar answered Sep 28 '22 05:09

enet