I am trying to generated a dynamic list of checkboxes that will be data bound to the Guid property from a Dictionary<Guid, string> variable. Then I have a separate "Select All" checkbox which will select all the checkboxes in the list. The IDs from the selected checkboxes will be stored a variable to be used in a query. The below code seems to set the IsSelected property of each checkbox just fine when I am debugging, but the screen is not re-rendering to display the new state. I am calling Invoke in the property setter, and calling StateHasChanged() in the parent component, but still there is no visible change. What am I missing?
ParentComponent.razor
<CustomCheckbox Label="Select All" @bind-Value="@IsAllBranchesSelected" @onchange="SelectAllBranchesChanged" style="margin-bottom:25px" />
<hr style="margin: 0px 0px 3px 0px;" />
@if (AdvancedSearchBranches != null && AdvancedSearchBranches.Any())
{
@if (Checkboxes != null && Checkboxes.Any())
{
@foreach (CheckBoxItemComponent cb in Checkboxes)
{
<CheckBoxItemComponent Value="@cb.Value" Text="@cb.Text" OnChange="Changed" />
}
}
}
@code {
public List<string> AdvancedSearchBranchIds = new List<string>();
public Dictionary<Guid, string> AdvancedSearchBranches = [];
public bool? IsAllBranchesSelected = false;
List<CheckBoxItemComponent> Checkboxes = new List<CheckBoxItemComponent>();
protected override async Task OnInitializedAsync()
{
LoadData();
}
private async void LoadData()
{
await GetBranchesAsync();
StateHasChanged();
}
private async Task GetBranchesAsync()
{
if (AdvancedSearchBranches.Any())
{
return;
}
Dictionary<Guid, string> _branches = await MyData.GetBranchesByCategoryAsync(Category);
AdvancedSearchBranches = _branches;
// create place holders in the list that will be replaced by the actual controls when the page renders
foreach (var item in AdvancedSearchBranches)
{
Checkboxes.Add(new CheckBoxItemComponent()
{
Value = item.Key.ToString(),
Text = item.Value,
Checked = false
});
}
}
public async Task SelectAllBranchesChanged(ChangeEventArgs e)
{
//select or unselect all
bool isSelected = e.Value as bool? ?? false;
foreach (var item in AdvancedSearchBranches)
{
string branchId = item.Key.ToString();
CheckBoxItemComponent? x = Checkboxes.Where(c => c.Value == branchId).FirstOrDefault();
if (x != null)
{
// This is getting set correctly, just not rendering
x.Checked = isSelected;
if (x.Checked)
{
if (!AdvancedSearchBranchIds.Contains(branchId))
{
AdvancedSearchBranchIds.Add(branchId);
}
}
else
{
if (AdvancedSearchBranchIds.Contains(branchId))
{
AdvancedSearchBranchIds.Remove(branchId);
}
}
}
}
StateHasChanged();
}
public void Changed()
{
foreach (var item in AdvancedSearchBranches)
{
string branchId = item.Key.ToString();
CheckBoxItemComponent? x = Checkboxes.Where(c => c.Value == branchId).FirstOrDefault();
if (x != null)
{
x.Checked = !x.Checked;
if (x.Checked)
{
if (!AdvancedSearchBranchIds.Contains(branchId))
{
AdvancedSearchBranchIds.Add(branchId);
}
}
else
{
if (AdvancedSearchBranchIds.Contains(branchId))
{
AdvancedSearchBranchIds.Remove(branchId);
}
}
}
}
}
}
CheckBoxItemComponent.razor
<input type="checkbox" @bind="@Checked" />@Text
<br />
@code
{
[Parameter]
public string Value { get; set; }
[Parameter]
public string Text { get; set; }
[Parameter] public Action? OnChange { get; set; }
private bool _Checked = false;
public bool Checked
{
get
{
return _Checked;
}
set
{
_Checked = value;
OnChange?.Invoke();
}
}
}`
It's impossible to build a Demo MRE from your code, so here's a single page demo showing the basics of what I think you are trying to achieve. You should be able to take this and build out your page.
Note I've created a state object and collection to manage the state. Manually creating instances of components is very wrong. You load the state object at start up. You read and transpose the state data into your database object when you want to save the data.
@page "/"
<PageTitle>Home</PageTitle>
<h1>Hello, world!</h1>
@foreach (var checkBoxState in _checkBoxStates)
{
<div class="mb-2">
<input class="form-check-input" type="checkbox" @bind="checkBoxState.IsChecked" />
@checkBoxState.Text
</div>
}
<div>
<button class="btn btn-success" @onclick="this.OnSelectAll">Select All</button>
<button class="btn btn-danger" @onclick="this.OnDeselectAll">Deselect All</button>
</div>
<div class="bg-dark text-white m-1 p-1">
<pre>Selected Items:</pre>
@foreach (var item in _selectedItems)
{
<pre>@item</pre>
}
</div>
@code {
private List<CheckBoxState> _checkBoxStates = new List<CheckBoxState>();
private IEnumerable<string?> _selectedItems => _checkBoxStates.Where(x => x.IsChecked).Select(x => x.Text) ?? Enumerable.Empty<string>();
protected override async Task OnInitializedAsync()
{
await Task.Yield();
// load data and transfer to _checkBoxStates
_checkBoxStates = new List<CheckBoxState>
{
new CheckBoxState { Value = "1", Text = "One", IsChecked = false },
new CheckBoxState { Value = "2", Text = "Two", IsChecked = true },
new CheckBoxState { Value = "3", Text = "Three", IsChecked = true },
};
}
private void OnSelectAll()
{
_checkBoxStates.ForEach(x => x.IsChecked = true);
}
private void OnDeselectAll()
{
_checkBoxStates.ForEach(x => x.IsChecked = false);
}
public class CheckBoxState
{
public string? Value { get; set; }
public string? Text { get; set; }
public bool IsChecked { get; set; }
}
}