Skip to content

[Bug]: Select<TValue> does not fire ValueChanged when selecting a SelectItem with null value (nullable TValue) #6456

@tntwist

Description

@tntwist

Blazorise Version

2.0.2

What Blazorise provider are you running on?

Bootstrap5

Link to minimal reproduction or a simple code snippet

Description contains steps to reproduce. Dont think a repro repo is really needed. If so let me know.

Steps to reproduce & bug description

When using Select<TValue> with a nullable TValue (e.g. TodoItemStatus?), selecting a SelectItem that has Value="null" does not trigger ValueChanged. This makes it impossible to use null as a valid selectable value, which is a common pattern for "All" / "None" / reset options.

Nullable types are documented as supported

The official documentation explicitly states:

Select and SelectItem are generic components and they support all of the basic value types line int, string, enum, etc. Nullable types are also supported.

Additionally, the SelectItem API documentation lists Value with a default of null, confirming that null is intended to be a valid value.

As a consumer, the natural expectation when reading "nullable types are supported" is that a SelectItem with Value="null" works as a selectable option. This is the standard pattern for "All" / "None" / "Reset" entries in dropdowns. Currently, nullable types are only partially supported — you can bind a nullable type, but you cannot select back to null once a non-null value has been chosen.

Prior issue

This was previously reported in #2921 (Sep 2021) and closed with a workaround recommendation. However, the underlying root cause was never fixed, and the documentation continues to advertise nullable type support without mentioning this limitation.

Steps to reproduce

<Select TValue="TodoItemStatus?" Value="@_status" ValueChanged="@OnStatusChanged">
    <SelectItem TValue="TodoItemStatus?" Value="null">All</SelectItem>
    <SelectItem TValue="TodoItemStatus?" Value="TodoItemStatus.NotStarted">Not Started</SelectItem>
    <SelectItem TValue="TodoItemStatus?" Value="TodoItemStatus.InProgress">In Progress</SelectItem>
    <SelectItem TValue="TodoItemStatus?" Value="TodoItemStatus.Completed">Completed</SelectItem>
</Select>

@code {
    private TodoItemStatus? _status;

    private Task OnStatusChanged(TodoItemStatus? value)
    {
        _status = value; // never called when selecting "All"
        return Task.CompletedTask;
    }
}
  1. Select "In Progress" → ValueChanged fires ✅
  2. Select "All" (null) → ValueChanged does not fire ❌

Root cause

The issue is in SelectItem.razor.cs:

protected string StringValue => Value?.ToString();

When Value is null, StringValue returns null. Blazor omits HTML attributes with null values, so the rendered <option> has no value attribute:

<!-- Rendered HTML -->
<option>All</option>

<!-- Expected -->
<option value="">All</option>

Per the HTML spec, an <option> without a value attribute uses its text content as the value. So when the user selects "All", the browser sends "All" instead of "".

This causes the following chain in BaseInputComponent.CurrentValueHandler:

  1. string.IsNullOrEmpty("All")false → does not enter the DefaultValue branch
  2. ParseValueFromStringAsync("All") is called
  3. Converters.TryChangeType<TodoItemStatus?>("All")fails (not a valid enum name)
  4. result.Success is falseSetCurrentValueAsync is never called
  5. ValueChanged never fires

Suggested fix

⚠️ Note: This suggested fix is AI-generated and has not been validated. It may require further review and testing.

In SelectItem.razor.cs, ensure StringValue always returns a non-null string so the value attribute is rendered:

// Before
protected string StringValue => Value?.ToString();

// After
protected string StringValue => Value?.ToString() ?? string.Empty;

This ensures <option value=""> is rendered for null values. The empty string then correctly triggers the string.IsNullOrEmpty check in CurrentValueHandler, which sets DefaultValue (null for nullable types) and fires ValueChanged.

Current workaround

Use Select<string> instead and manually parse the enum:

<Select TValue="string" Value="@_statusValue" ValueChanged="@OnStatusChanged">
    <SelectItem TValue="string" Value="@string.Empty">All</SelectItem>
    @foreach (var status in Enum.GetValues<TodoItemStatus>())
    {
        <SelectItem TValue="string" Value="@status.ToString()">@status</SelectItem>
    }
</Select>

@code {
    private string _statusValue = string.Empty;

    private Task OnStatusChanged(string value)
    {
        _statusValue = value;
        var parsed = Enum.TryParse<TodoItemStatus>(value, out var s) ? s : (TodoItemStatus?)null;
        return Task.CompletedTask;
    }
}

This workaround is functional but forces consumers to abandon the generic nullable type support that is explicitly documented. It introduces manual string-to-enum conversion boilerplate that should not be necessary.

What is expected?

One of the following should happen:

  1. Fix the bug: Apply the suggested fix (or an equivalent) so that SelectItem with a null value works correctly, as the documentation promises.
  2. Remove nullable support from the documentation: If nullable SelectItem values are not intended to work, remove the "Nullable types are also supported" statement from the docs to avoid misleading consumers.
  3. Document the limitation: At a minimum, add a visible note to the Select documentation that clarifies: while nullable TValue binding is supported, selecting back to null via a SelectItem with Value="null" does not work, and a workaround is required.

What is actually happening?

See description.

What browsers do you see the problem on?

No response

Any additional comments?

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions