Skip to content

Commit e74a5cf

Browse files
[release/8.0] [Blazor] Blazor InputRadio is broken by IHandleEvent (#53270)
Backport of #53245 to release/8.0 /cc @javiercn # [Blazor] Blazor InputRadio is broken by IHandleEvent Update `InputRadioContext` to get the `CurrentValue` directly from the `InputRadioGroup` rather than using a copy that needs to be updated. ## Description Input radio groups did not update their values correctly when the component that used them implemented IHandleEvent. When the user clicked on the input radio, the value would not be updated in the UI. Fixes #52069 ## Customer Impact Input radio elements are not correctly updated when the component that uses them implements IHandleEvent. ## Regression? - [X] Yes - [ ] No 7.0 ## Risk - [ ] High - [ ] Medium - [X] Low [Justify the selection above] ## Verification - [ ] Manual (required) - [X] Automated ## Packaging changes reviewed? - [ ] Yes - [ ] No - [x] N/A ---- ## When servicing release/2.1 - [ ] Make necessary changes in eng/PatchConfig.props --------- Co-authored-by: jacalvar <[email protected]>
1 parent 37c8f0d commit e74a5cf

File tree

6 files changed

+58
-10
lines changed

6 files changed

+58
-10
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.AspNetCore.Components;
5+
6+
internal interface IInputRadioValueProvider
7+
{
8+
public object? CurrentValue { get; }
9+
}

src/Components/Web/src/Forms/InputRadioContext.cs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,19 @@ namespace Microsoft.AspNetCore.Components.Forms;
88
/// </summary>
99
internal sealed class InputRadioContext
1010
{
11+
private readonly IInputRadioValueProvider _valueProvider;
12+
1113
public InputRadioContext? ParentContext { get; }
1214
public EventCallback<ChangeEventArgs> ChangeEventCallback { get; }
15+
public object? CurrentValue => _valueProvider.CurrentValue;
1316

1417
// Mutable properties that may change any time an InputRadioGroup is rendered
1518
public string? GroupName { get; set; }
16-
public object? CurrentValue { get; set; }
1719
public string? FieldClass { get; set; }
1820

19-
/// <summary>
20-
/// Instantiates a new <see cref="InputRadioContext" />.
21-
/// </summary>
22-
/// <param name="parentContext">The parent context, if any.</param>
23-
/// <param name="changeEventCallback">The event callback to be invoked when the selected value is changed.</param>
24-
public InputRadioContext(InputRadioContext? parentContext, EventCallback<ChangeEventArgs> changeEventCallback)
21+
public InputRadioContext(IInputRadioValueProvider valueProvider, InputRadioContext? parentContext, EventCallback<ChangeEventArgs> changeEventCallback)
2522
{
23+
_valueProvider = valueProvider;
2624
ParentContext = parentContext;
2725
ChangeEventCallback = changeEventCallback;
2826
}

src/Components/Web/src/Forms/InputRadioGroup.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Components.Forms;
1010
/// <summary>
1111
/// Groups child <see cref="InputRadio{TValue}"/> components.
1212
/// </summary>
13-
public class InputRadioGroup<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TValue> : InputBase<TValue>
13+
public class InputRadioGroup<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TValue> : InputBase<TValue>, IInputRadioValueProvider
1414
{
1515
private readonly string _defaultGroupName = Guid.NewGuid().ToString("N");
1616
private InputRadioContext? _context;
@@ -27,14 +27,16 @@ public class InputRadioGroup<[DynamicallyAccessedMembers(DynamicallyAccessedMemb
2727

2828
[CascadingParameter] private InputRadioContext? CascadedContext { get; set; }
2929

30+
object? IInputRadioValueProvider.CurrentValue => CurrentValue;
31+
3032
/// <inheritdoc />
3133
protected override void OnParametersSet()
3234
{
3335
// On the first render, we can instantiate the InputRadioContext
3436
if (_context is null)
3537
{
3638
var changeEventCallback = EventCallback.Factory.CreateBinder<string?>(this, __value => CurrentValueAsString = __value, CurrentValueAsString);
37-
_context = new InputRadioContext(CascadedContext, changeEventCallback);
39+
_context = new InputRadioContext(this, CascadedContext, changeEventCallback);
3840
}
3941
else if (_context.ParentContext != CascadedContext)
4042
{
@@ -59,7 +61,7 @@ protected override void OnParametersSet()
5961
// Otherwise, just use a GUID to disambiguate this group's radio inputs from any others on the page.
6062
_context.GroupName = _defaultGroupName;
6163
}
62-
_context.CurrentValue = CurrentValue;
64+
6365
_context.FieldClass = EditContext?.FieldCssClass(FieldIdentifier);
6466
}
6567

src/Components/test/E2ETest/Tests/FormsTest.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -844,6 +844,30 @@ public void InputRadioGroupWorksWithMutatingSetter()
844844
Browser.Equal("False", () => tuesday.GetDomProperty("checked"));
845845
}
846846

847+
[Theory]
848+
[InlineData(0)]
849+
[InlineData(2)]
850+
public void InputRadioGroupWorksWithParentImplementingIHandleEvent(int n)
851+
{
852+
Browser.Url = new UriBuilder(Browser.Url) { Query = ($"?n={n}") }.ToString();
853+
var appElement = Browser.MountTestComponent<InputRadioParentImplementsIHandleEvent>();
854+
var zero = appElement.FindElement(By.Id("inputradiogroup-parent-ihandle-event-0"));
855+
var one = appElement.FindElement(By.Id("inputradiogroup-parent-ihandle-event-1"));
856+
857+
Browser.Equal(n == 0 ? "True" : "False", () => zero.GetDomProperty("checked"));
858+
Browser.Equal("False", () => one.GetDomProperty("checked"));
859+
860+
// Observe the changes after a click
861+
one.Click();
862+
Browser.Equal("False", () => zero.GetDomProperty("checked"));
863+
Browser.Equal("True", () => one.GetDomProperty("checked"));
864+
865+
// Ensure other options can be selected
866+
zero.Click();
867+
Browser.Equal("False", () => one.GetDomProperty("checked"));
868+
Browser.Equal("True", () => zero.GetDomProperty("checked"));
869+
}
870+
847871
[Fact]
848872
public void InputSelectWorksWithMutatingSetter()
849873
{
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
@using Microsoft.AspNetCore.Components.Forms
2+
@implements IHandleEvent
3+
4+
<InputRadioGroup @bind-Value="N">
5+
<InputRadio id="inputradiogroup-parent-ihandle-event-0" Value="0" />
6+
<InputRadio id="inputradiogroup-parent-ihandle-event-1" Value="1" />
7+
</InputRadioGroup>
8+
9+
@code {
10+
11+
[SupplyParameterFromQuery(Name = "n")] int? N { get; set; }
12+
13+
Task IHandleEvent.HandleEventAsync(EventCallbackWorkItem callback, object arg) => callback.InvokeAsync(arg);
14+
}

src/Components/test/testassets/BasicTestApp/Index.razor

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
<option value="BasicTestApp.FormsTest.InputRangeComponent">Input range</option>
5050
<option value="BasicTestApp.FormsTest.InputsWithoutEditForm">Inputs without EditForm</option>
5151
<option value="BasicTestApp.FormsTest.InputsWithMutatingSetters">Inputs with mutating setters</option>
52+
<option value="BasicTestApp.FormsTest.InputRadioParentImplementsIHandleEvent">Input Radio Parent Implements IHandleEvent</option>
5253
<option value="BasicTestApp.NavigateOnSubmit">Navigate to submit</option>
5354
<option value="BasicTestApp.GlobalizationBindCases">Globalization Bind Cases</option>
5455
<option value="BasicTestApp.GracefulTermination">Graceful Termination</option>

0 commit comments

Comments
 (0)