RBE
RBE

Reputation: 41

Blazor List<T> re-rendering missing when T element changed

I try to update a list of objects when I update an object from a form component. But event if my object is well updated, my list is not.

Here my parent component

@inject ApiClient Api

@if (normes != null)
{
    <ul>
        @foreach (var item in normes)
        {
            <li @onclick="@(()=> norme = item)">@item.Name</li>
        }
    </ul>
}

@if (norme != null)
{
    <label>The bind parameter in the parent: @norme.Name</label>
    <Edit @bind-Norme="norme" />
}

@code {

    private List<Norme> normes { get; set; }

    private Norme norme { get; set; }

    protected override async Task OnInitializedAsync()
    {
        normes = await Api.GetAsync<List<Norme>>("norme");
    }
}

I get a list from an api and when I select a Norme à display a form component to edit it.

Here my child component:

@inject ApiClient Api

<Loader WaitFor="localNorme">
    <EditForm Model="localNorme" OnValidSubmit="OnValidSubmit">
        <DataAnnotationsValidator />
        
        @if (localNorme.Id == 0)
        {
            <h3>Nouvelle norme</h3>
        }
        else
        {
            <h3>Norme n° @localNorme.Id</h3>
        }

        <div class="btn-toolbar justify-content-end" role="toolbar">
            <div class="btn-group mr-2" role="group">
                <button type="submit" class="btn btn-outline-success"><i class="bi-save" /></button>
                <button type="button" class="btn btn-outline-danger" @onclick="OnCancelClick"><i class="bi-x" /></button>
            </div>
        </div>


        <div class="overflow-auto">

            <div class="form-group">
                <FormLabel For="@(()=>localNorme.Name)" />
                <InputText @bind-Value="localNorme.Name" class="form-control" />
                <ValidationMessage For="@(()=>localNorme.Name)" />
            </div>

            <input type="hidden" @bind-value="localNorme.Id" />
        </div>
    </EditForm>

    <label>The bind parameter in the child: @Norme.Name</label>
</Loader>


@code {
    [Parameter] public Norme Norme { get; set; }

    [Parameter] public EventCallback<Norme> NormeChanged { get; set; }

    private Norme localNorme;

    protected override async Task OnParametersSetAsync()
    {
        await LoadAsync();
    }

    private async Task LoadAsync()
    {
        if (Norme.Id > 0)
        {
            localNorme = await Api.GetAsync<Norme>($"norme/{Norme.Id}");
        }
        else
        {
            localNorme = new Norme();
        }
    }

    private async Task OnValidSubmit(EditContext context)
    {
        Norme model = context.Model as Norme;
        if (model.Id > 0)
        {
            // Update
            model = await Api.PutAsync<Norme>($"norme/{Norme.Id}", model);
        }
        else
        {
            // Create
            model = await Api.PostAsync<Norme>("norme", model);
        }
        await NormeChanged.InvokeAsync(model); // bound object update
    }

    private async Task OnCancelClick()
    {
        await LoadAsync();
    }
}

Here the result when I have submitted my form: enter image description here

As you can see, my list is not updated.

At 1st, in my child component I used my Norme parameter as model in my form. But my parent parameter and List were updated on my input's OnChanged event. So I insert a localNorme as model in my form and when it's submit I use the EventCallback to update the bound parameter. In my parent, my parameter is well updated but I don't know what I miss to fire my List update.

I try to insert some StateHasChanged() but nothing changed. I also check if my List is updated if I update a norme's property in the parent.

Thank you.

Upvotes: 1

Views: 1513

Answers (1)

Mister Magoo
Mister Magoo

Reputation: 9019

Your list you want to update is "normes" I think.

You set that list here:

protected override async Task OnInitializedAsync()
{
    normes = await Api.GetAsync<List<Norme>>("norme");
}

Which is only called when the parent is first added to the render tree.

You need a method of updating it when an item has been changed.

The simple way would be to manually bind the Edit component so you can register a handler for the NormeChanged callback

<Edit Norme="norme" NormeChanged=RefreshList/>

And then RefreshList can update the list "normes" and refresh the parent component

async Task RefreshList(Norme norme) 
{
  normes = await Api.GetAsync<List<Norme>>("norme");
  StateHasChanged();
}

Of course there are many ways to do this - centralised state, event aggregator, message bus - this is the simplest to show here.

Upvotes: 2

Related Questions