Update UI when model bound to component is changed in Blazor

Viewed 3067

I've created a simplified version of my code to demostrate the issue I'm having. I have a custom Blazor component, TestComponent.razor:

@{ int index = 0; }
@foreach (var option in _value)
{
    <input id="@index" type="checkbox" @bind="@option.Selected" /><label for="@index">@option.Name</label>
    index++;
}

@code {
    [Parameter] public EventCallback<TestModel[]> ValueChanged { get; set; }
    private TestModel[] _value;
    [Parameter] public TestModel[] Value
    {
        get => _value;
        set
        {
            if (_value == value) return;
            _value = value;
            ValueChanged.InvokeAsync(value);
        }
    }
}

I have a basic model, TestModel.cs:

public class TestModel
{
    public string Name { get; set; }
    public bool Selected { get; set; }
}

My page simply binds an array of my TestModel to the TestComponent and shows all selected options:

@page "/"

<TestComponent @bind-Value="testOptions" />
@foreach (var option in testOptions)
{
    if (option.Selected)
    {
        <p>@option.Name</p>
    }
}

@code {
    private TestModel[] testOptions = new TestModel[]
    {
        new TestModel() { Name = "Amy", Selected = true },
        new TestModel() { Name = "Bob", Selected = false }
    };
}

On loading the page I get a list of selected options. When I check/uncheck any of the options I can see that the model updates (I added a button which writes the model to the console) but the UI is not updated. Please could someone advise what it is I'm missing to get the UI to update the list of selected options?

1 Answers

Thanks to Jason for his help in getting me to a solution for this. I got this working by adding StateHasChanged() to a newly created OnChangeAsync method. This requires a bit of fiddling around because my model is an array and each checkbox changes just one member of that array.

New TestComponent.razor:

    @for (int i = 0; i < _value.Length; i++)
    {
        int index = i;
        <input id="@index" type="checkbox" checked="@_value[index].Selected" @onchange="async args => { await OnChangeAsync(args, index); }" /><label for="@index">@_value[index].Name</label>
    }
    
    @code {
        [Parameter] public EventCallback<TestModel[]> ValueChanged { get; set; }
        private TestModel[] _value;
        [Parameter] public TestModel[] Value
        {
            get => _value;
            init => _value = value;
        }
    
        public async Task OnChangeAsync(ChangeEventArgs args, int index)
        {
            _value[index].Selected = (bool)args.Value;
            await ValueChanged.InvokeAsync(_value);
            StateHasChanged();
        }
    }
Related