'StateHasChanged for specific component instead rerender all components in a page

I am using multiple components is my application each are rendered in conditional manner. Is there any possibilities to re-render a specific component alone?

MyRazor.razor file,

 <button @onclick="Chnage">Second</button>
<div>
    @if (renderOne)
    {
        var data1 = Count++;
        <ComponentFirst>@data1</ComponentFirst>
    }
    @if (renderTwo)
    {
        var data2 = Count2++;
        <ComponentSecond class="btn-danger">@data2</ComponentSecond>
     }
</div>
@code { 
    void Chnage()
    {
        renderOne = true;
    }
}

ComponentFirst and ComponentSecond are rendered by checking by respective booleans. In the button click, I have enable CompoenentFirst alone. But the ComponentSecond also rendered again. My aim is If I enable renderOne ComponentFirst alone should be rendered again. If I disable renderTwo ComponentTwo alone should rendered again instead of rendering both components for a single change in the application.



Solution 1:[1]

You probably shouldn't be worried about a component rendering lots of times. Rendering only builds a rendertree, it doesn't update the browser's DOM. Only once the whole page's render tree is built will Blazor compare it against the last render tree and then update the DOM from the diff.

Having said that:

If the parent passes any information to the child component via a [Parameter] property (including a RenderFragment) then whenever the parent component re-renders the child component will also re-render just in case anything has altered.

If you want components to re-render independently of their parents then you should not pass any state down to them. You can achieve this by storing state outside of the components. For example

public class MyState
{
  public int Count1 { get; set; }
  public int Count2 { get; set; }

  public event EventHandler<EventArgs> Changed;
  public void NotifyChanged() => Changed?.Invoke(this, EventArgs.Empty);
}

If you register that as an injectable dependency then you can consume it directly in the child component

@inject MyState State
@implements IDisposable

<div>
  The value is @State.Count1
</div>

@code
{
  protected override void OnInitialized()
  {
    State.Changed += DoUpdate;
  }

  void IDisposable.Dispose()
  {
    State.Changed -= DoUpdate; // Important1
  }

  private void DoUpdate(object sender, EventArgs e)
  {
    InvokeAsync(StateHasChanged);
  }
}

Any component can update the state by injecting MyState and then doing

injectedState.Counter1++;
injectedState.Counter2--;
injectedState.NotifyChanged();

Solution 2:[2]

Related to Peter Morris' answer about passing state directly to child components, what I do is create a generic class that wraps an object with an OnChange event which gets invoked in the setter:

public class Subscribable<T>
{
    public Subscribable() { }
    public Subscribable(T initialValue) { _value = initialValue; }

    private T? _value;

    public T? Value
    {
        get => _value;
        set
        {
            _value = value;
            OnChange?.Invoke();
        }
    }

    public event Action? OnChange;
}

Then you can wrap any object in your injected MyState class with this Subscribable class:

public class MyState
{
    public Subscribable<int> Count1 { get; } = new(0);
    public Subscribable<int> Count2 { get; } = new(0);
    public int Count3 { get; set; }
}

When you inject MyState into the child component, you can tell the component to re-render when any Subscribable object changes by registering StateHasChanged with that objects' OnChange event:

@inject MyState State
@implements IDisposable

<p>
  @State.Count1.Value
  <br />
  This component will re-render when Count1 changes.
</p>
<p>
  @State.Count2.Value
  <br />
  This component will not re-render when Count2 changes, because
  StateHasChanged was not registered with the OnChange event for Count2.
</p>
<p>
  @State.Count3
  <br />
  When Count3 changes, nothing will re-render anywhere, because there is no 
  mechanism defined to tell any component that Count3 has changed.
</p>

@code
{
    protected override void OnInitialized()
    {
        State.Count1.OnChange += StateHasChanged;
    }

    void IDisposable.Dispose()
    {
        State.Count1.OnChange -= StateHasChanged;
    }
}

You can thus tightly control rendering at the property level of your injected state.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Peter Morris
Solution 2