Description
Is there an existing issue for this?
- I have searched the existing issues
Describe the bug
If you have a <textarea>, which needs to get its data from a method, not a bindable property, and we also want to save any changes made by the user to the text in that <textarea>, one may think to write the following code:
<textarea rows="3" style="width: 350px;" @onchange="Text_OnChange">@GetData(someParameter)</textarea>
That code will initialize the <textarea> with the string from the GetData(someParameter)
method, which is placed between the opening and closing tag of the <textarea>. This is the recommended practice for initializing a <textarea> in HTML. What I mean by that is, according to https://www.w3schools.com/tags/tag_textarea.asp, a <textarea> does not, for example, have/support a value
attribute like other <input>
elements.
Then, because we also want to get any changes that the user may make to the text, we wire up the onchange
event. This should conceptually and effectively give us two-way binding of the <textarea>. In the first example of the following documentation, https://learn.microsoft.com/en-us/aspnet/core/blazor/components/data-binding?view=aspnetcore-6.0, it says the same thing:
As a demonstration of how data binding composes in HTML, the following example binds the InputValue property to the second element's value and onchange attributes (change). The second element in the following example is a concept demonstration and isn't meant to suggest how you should bind data in Razor components.
Again, the reason why we don't simply just use a @bind
attribute on the <textarea> is because we don't have a bindable property. The data is coming from a method.
Initially, this code appears to work. However, in the following complete example, it does not work as expected. You can download the example from the repo, but I'll also outline and include most of the details and code directly here too for others to quickly see and read.
Create a new solution/project using either the Blazor WebAssembly App or Blazor Server App project templates:
In the Index.razor
page, replace the code with the following:
@page "/"
<PageTitle>Index</PageTitle>
<h1>Instructions</h1>
<br/>
<ol>
<li>Click one of the links below. A textarea and text box will be rendered with some initial text.</li>
<li>Click back and forth between each link. Notice that both textarea and text box will be populated
with, and change between, the initial/default values assigned to each link.
</li>
<li>Click the Method 1 link, then change the value of the text in the <b>textarea</b>.</li>
<li>Then click the Method 2 link. Notice that the text in the textarea did not get updated/changed
to the Method 2 text, as the text box did. This doesn't seem like correct behavior.
</li>
</ol>
<a href="./?method=method1">Method 1</a>
<br />
<a href="./?method=method2">Method 2</a>
@if (!String.IsNullOrWhiteSpace(Method))
{
<form>
@* In HTML, according to https://www.w3schools.com/tags/tag_textarea.asp, the text that is to be
displayed in the textarea should be placed between the opening and closing <textarea> tags. *@
<textarea rows="3" style="width: 350px;" @onchange="Text_OnChange">@GetData(Method)</textarea>
<br />
<input type="text" style="width: 350px;" value="@GetData(Method)" @onchange="Text_OnChange" />
<br/>
<br/>
@* If, however, we make the <textarea> similar to the <input type="text"> by specifying a 'value'
attribute, then the behavior matches that of the <input type="text">, as desired. However, according
to https://www.w3schools.com/tags/tag_textarea.asp, the <textarea> doesn't have/support a 'value'
attribute.
(Also, while this workaround happens to work in this simple sample, I have another project where
this workaround doesn't fix the problem.) *@
@* Uncomment the following code to text this workaround. *@
@*<textarea rows="3" style="width: 350px;" value="@GetData(Method)" @onchange="Text_OnChange"></textarea>*@
</form>
}
@code
{
private Dictionary<string, string> cache = new Dictionary<string, string>
{
{ "method1", "Initial value for method 1" },
{ "method2", "Method 2 initial value." },
};
[Parameter, SupplyParameterFromQuery(Name = "method")]
public string? Method { get; set; }
protected override Task OnInitializedAsync()
{
return base.OnInitializedAsync();
}
protected override void OnParametersSet()
{
base.OnParametersSet();
}
private void Text_OnChange(ChangeEventArgs e)
{
Console.WriteLine($"Text_OnChange: {e.Value}");
if (Method != null && cache.ContainsKey(Method))
{
cache[Method] = (string)e.Value!;
}
}
private string GetData(string key)
{
Console.WriteLine($"Data for {key} is: {cache[key]}");
return cache[key];
}
}
Run/debug the app and follow the instructions by clicking on both links to see the initial data:
and
Then change the text in the <textarea> of the first link:
Finally, click on the "Method 2" link and notice that the text in the <textarea> is not updated:
As you see in the example, I have also include an <input type="text">
element. With an <input type="text">
element, the proper way to set/initialize the text value is to set the value
attribute. So we can accomplish what we need to do, that is, conceptually/effectively perform two-way binding by setting the value
attribute to the result of the GetData(someParameter)
method and wiring up and handling the @onchange
event.
If you noticed in the last screen shot, the text in the regular text box was correctly updated. So why wasn't the <textarea>? We can take it a step further and have the user change the text in the regular text box too, then click back and forth between the two links and the text in the text box is always updated correctly:
So why is the behavior between those to, very similar elements, different?
Doing more tests on my own, I also discovered that if you change the <textarea> to instead use/have a value
attribute, which again, according to the HTML specification is not a valid attribute, then the behavior is as expected and matches that of the regular <input type="text">
element. At least, in this simple example, that workaround yields the correct/desired behavior. However, in my real project, that workaround still doesn't give the correct/expected behavior.
I have found and read the following:
#45797
I have also read the following, that is new in .NET 7.
https://learn.microsoft.com/en-us/aspnet/core/blazor/components/data-binding?view=aspnetcore-7.0#use-bindgetbindset-modifiers-and-avoid-event-handlers-for-two-way-data-binding
But in my example, I'm not trying to do. Specifically, I am not attempting to modify the value that the user enters and expect Blazor to render that modified value.
I have not tried the new @bind:get/@bind:set
modifiers available in .NET 7 to know whether or not it would fix my problem. But if the new @bind:get
only supports binding to a C# property, then no, that won't solve my problem because I need to pass in a parameter to a method.
Expected Behavior
Having said all of that, I have worked around all of the issues, but I wanted to report this nonetheless and at least try to get an answer to the following:
Why, in the provided example code, is there a behavior difference between a <textarea> that is written as:
<textarea rows="3" style="width: 350px;" @onchange="Text_OnChange">@GetData(Method)</textarea>
versus being written as:
<textarea rows="3" style="width: 350px;" value="@GetData(Method)" @onchange="Text_OnChange"></textarea>
To me, the second one, that uses the value
attribute, shouldn't even be allowed. But at a minimum, isn't it a bug that those two behave differently?
Steps To Reproduce
https://github.com/Kevin-Andrew/BlazorTextAreaNotUpdating
Exceptions (if any)
No response
.NET Version
.NET 6
Anything else?
No response