dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.46k stars 10.03k forks source link

Checkbox not re-rendering correctly when value of last clicked checkbox is changed in code #56847

Open ThinkElevenSean opened 3 months ago

ThinkElevenSean commented 3 months ago

Is there an existing issue for this?

Describe the bug

We have written a Blazor component to represent a check box list. We want it to 'self-check' one of the options should all of the options be de-selected, so that it is not possible to select no options. This is working OK, unless the last option we de-select is the 'default' option that we want to be selected if no other option is. In this case the underlying value is set correctly, but the checkbox is not rendered correctly.

When stepping through the debugger we can see that that the underlying value is set correctly and the component is re-rendered, but the 'checked' attribute does not appear to be reflected in what we see.

Expected Behavior

We would expect the checkbox to be rendered to correctly reflect it's underlying value. We have tried every sensible and stupid solution, playing around with rendering and parameter set events but have not been able to make this happen no matter what we do.

Steps To Reproduce

https://github.com/ThinkElevenSean/CheckBoxListTest

Steps to reproduce:

Exceptions (if any)

No response

.NET Version

8.0

Anything else?

No response

MariovanZeist commented 3 months ago

Hi @ThinkElevenSean The "checked" attribute on an input only reflects the correct state on page load, it will not reflect the current state of the input. nor does setting the attribute after the page has loaded and the component has been created in the dom set the input to "checked" again More info here

So that's why you are seeing this apparent mismatch. You can fix this issue by using blazors InputCheckBox component, it has some internal logic to update the checkbox state properly.

Modify the input part of your razor file: To the following:

<label>
    <InputCheckbox id="@item.Text" @bind-Value=item.Selected @bind-Value:after="() => Clicked(item)" />
    @item.Text
</label>

The Clicked function is used to set the default value again when no options are selected.

Remove the CheckboxClicked function in your .cs file and replace it with:

private void Clicked(CheckBoxListItem item)
{
  if (CurrentValue is not null)
  {
    if (CurrentValue.All(x => !x.Selected) && DefaultSelectedIndex is not null && CurrentValue.Count > DefaultSelectedIndex)
    {
      CurrentValue[DefaultSelectedIndex.Value].Selected = true;
    }
  }
}
ThinkElevenSean commented 3 months ago

Hi @MariovanZeist

We prefer not to use Blazor components in our own components, as is the recommendation of many developers. We have found it often causes us issues and things work a lot better when we use plain HTML components. The sample project I have uploaded is a very simplified version of our actual component, in reality it's a lot more complicated and using Blazor form components inside it often leads to binding and other issues.

I understand what you're saying about what HTML is rendered, I know that the checked attribute doesn't get updated dynamically, however the 'checked' property on the underlying HTMLInputElement of the checkbox should (as per the documentation you linked to) and this is happening in steps 1 to 3 of my steps to reproduce above - up until that point the selected status of the checkbox is rendered correctly based on the underlying value and 'Item 1' is correctly selected, even though no 'checked' attribute is rendered. What we don't understand is why it doesn't work if you deselect the option that needs to be re-checked when no others are.