Skip to content

Commit 36be16b

Browse files
Fix binding <select> to a null value (#23221)
1 parent adbedd2 commit 36be16b

File tree

5 files changed

+25
-7
lines changed

5 files changed

+25
-7
lines changed

src/Components/Web.JS/dist/Release/blazor.server.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Components/Web.JS/dist/Release/blazor.webassembly.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Components/Web.JS/src/Rendering/BrowserRenderer.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -255,8 +255,8 @@ export class BrowserRenderer {
255255
// added as an opaque markup block rather than individually
256256
// Right here we implement [2]
257257
if (newDomElementRaw instanceof HTMLSelectElement && selectValuePropname in newDomElementRaw) {
258-
const selectValue = newDomElementRaw[selectValuePropname];
259-
newDomElementRaw.value = selectValue;
258+
const selectValue: string | null = newDomElementRaw[selectValuePropname];
259+
setSelectElementValue(newDomElementRaw, selectValue);
260260
}
261261
}
262262

@@ -357,16 +357,20 @@ export class BrowserRenderer {
357357
case 'SELECT':
358358
case 'TEXTAREA': {
359359
const value = attributeFrame ? frameReader.attributeValue(attributeFrame) : null;
360-
(element as any).value = value;
361360

362-
if (element.tagName === 'SELECT') {
361+
if (element instanceof HTMLSelectElement) {
362+
setSelectElementValue(element, value);
363+
363364
// <select> is special, in that anything we write to .value will be lost if there
364365
// isn't yet a matching <option>. To maintain the expected behavior no matter the
365366
// element insertion/update order, preserve the desired value separately so
366367
// we can recover it when inserting any matching <option> or after inserting an
367368
// entire markup block of descendants.
368369
element[selectValuePropname] = value;
370+
} else {
371+
(element as any).value = value;
369372
}
373+
370374
return true;
371375
}
372376
case 'OPTION': {
@@ -519,3 +523,15 @@ function stripOnPrefix(attributeName: string) {
519523

520524
throw new Error(`Attribute should be an event name, but doesn't start with 'on'. Value: '${attributeName}'`);
521525
}
526+
527+
function setSelectElementValue(element: HTMLSelectElement, value: string | null) {
528+
// There's no sensible way to represent a select option with value 'null', because
529+
// (1) HTML attributes can't have null values - the closest equivalent is absence of the attribute
530+
// (2) When picking an <option> with no 'value' attribute, the browser treats the value as being the
531+
// *text content* on that <option> element. Trying to suppress that default behavior would involve
532+
// a long chain of special-case hacks, as well as being breaking vs 3.x.
533+
// So, the most plausible 'null' equivalent is an empty string. It's unfortunate that people can't
534+
// write <option value=@someNullVariable>, and that we can never distinguish between null and empty
535+
// string in a bound <select>, but that's a limit in the representational power of HTML.
536+
element.value = value || '';
537+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ public void CanBindSelect()
219219
// https://github.com/dotnet/aspnetcore/issues/17735
220220
target.SelectByText("Empty value");
221221
Browser.Equal(string.Empty, () => boundValue.Text);
222+
Browser.Equal("Empty value", () => target.SelectedOption.Text);
222223
}
223224

224225
[Fact]
@@ -237,6 +238,7 @@ public void CanBindSelectToMarkup()
237238
// https://github.com/dotnet/aspnetcore/issues/17735
238239
target.SelectByText("Empty value");
239240
Browser.Equal(string.Empty, () => boundValue.Text);
241+
Browser.Equal("Empty value", () => target.SelectedOption.Text);
240242
}
241243

242244
[Fact]

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@
241241
<select id="select-box" @bind="selectValue">
242242
<optgroup label="Some choices">
243243
<!-- Show it also works with optgroup -->
244-
<option value="">Empty value</option>
244+
<option value=@string.Empty>Empty value</option>
245245
<option value=@SelectableValue.First>First choice</option>
246246
<option value=@SelectableValue.Second>Second choice</option>
247247
<option value=@SelectableValue.Third>Third choice</option>

0 commit comments

Comments
 (0)