Search code examples
data-bindingblazor

Blazor Child Component Rendering and Parameter Changes


I have a component that accepts an int parameter - which the component uses to make an API call to retrieve some data. This logic is currently in the component's OnParametersSetAsync().

This component also has a complex-typed parameter.

When this component is used by a parent component that re-renders itself for various reasons, OnParametersSetAsync() is called on this child component - even if none of its parameters have changed. My understanding is that this is because of the complex-type parameter (blazor can't tell if it changed, so it assumes it did).

This results in the API call retriggering needlessly (the actual int parameter didn't change).

Is doing data-retrieval like this not appropriate for OnParametersSetAsync()? If so, how should I change my components to work with the Blazor framework?

Parent Component

Call to ChangeName() triggers the re-render of the parent

<div>
    <EditForm Model="favoriteNumber">
        <InputSelect @bind-Value="favoriteNumber">
            <option value="0">zero</option>
            <option value="1">one</option>
            <option value="2">two</option>
            <option value="3">three</option>
        </InputSelect>
    </EditForm>
    
    @* This is the child-component in question *@
    <TestComponent FavoriteNumber="favoriteNumber" FavoriteBook="favoriteBook" />
    <br />

    <EditForm Model="person">
        First Name:
        <InputText @bind-Value="person.FirstName" />
        <br />
        Last Name:
        <InputText @bind-Value="person.LastName" />
    </EditForm>

    <button @onclick="ChangeName">Change Name</button>
</div>

@code {
    private int favoriteNumber = 0;
    private Book favoriteBook = new();
    private Person person = new() { FirstName = "Joe", LastName = "Smith" };

    private void ChangeName()
    {
        person.FirstName = person.FirstName == "Susan" ? "Joe" : "Susan";
        person.LastName = person.LastName == "Smith" ? "Williams" : "Smith";
    }
}

Child Component

<div>@infoAboutFavoriteNumber</div>

@code {
    [Parameter]
    public int FavoriteNumber { get; set; }

    [Parameter]
    public Book FavoriteBook { get; set; }

    private string infoAboutFavoriteNumber = "";

    protected override async Task OnParametersSetAsync()
    {
        infoAboutFavoriteNumber = await ApiService.GetAsync<string>(id: FavoriteNumber.ToString());
    }
}

Solution

  • which the component uses to make an API call to retrieve some data.

    Your child component should not perform any API calls. It is the parent component that should manage the state of the parent itself and its children, downstreaming the data. If things get complicated, then you'll have to implement a service that handle state. @Peter Morris would certainly advise you to use Blazor State Management Using Fluxor.

    Not sure why you use two EditForm components, when you actually should use none. Realize that components are very expensive, and they make your code slow. So use it wisely

    Answering your question:

    Define a local field to hold the FavoriteNumber parameter property's value as follows, in the child component:

     @code 
      {
           [Parameter]
           public int FavoriteNumber { get; set; }
           private int FavoriteNumberLocal = -1;
      }
    

    Note: The FavoriteNumberLocal variable stores the value passed from the parent component. It allows you to locally store and check if its value has changed, and accordingly decide whether to call the Web Api end point or not (Again, you shouldn't do that like this)

    protected override async Task OnParametersSetAsync()
    {
        if( FavoriteNumberLocal != FavoriteNumber)
        { 
             FavoriteNumberLocal = FavoriteNumber;
    
             infoAboutFavoriteNumber = await ApiService.GetAsync<string>(id: 
             FavoriteNumberLocal.ToString());
        }
    }  
    

    Read the last two comments to this question