Reputation: 9439
When I have a one-way binding on a HTML element and accompanying binding on onchange
that doesn't always change it, then how do I tell or allow Blazor to "refresh" that element's value from its bound value that hasn't changed?
StateHasChanged() doesn't work, presumably because Blazor detects that there has been no change.
Minimalist but absurd example (note that the real-world case is not absurd):
@page "/"
<select value="@Foo" @onchange="Update">
<option>First</option>
<option>Second</option>
</select>
<div>
Value of Foo: @Foo
</div>
@code {
private string Foo {get;set;} = "First";
private void Update(ChangeEventArgs args) {
return; // doesn't actually change it
}
}
Thus the new value in the HTML element shows immediately on changing it, but the bound value never actually changes. Working demo here: how do I get the value of the drop down to remain (or change back to) "First", which is the unchanged value stored in Foo?
Upvotes: 2
Views: 5123
Reputation: 30016
The problem is with the Renderer and Diffing engine.
The value has changed in the Browser DOM, but not in the Renderer DOM. This is one of the few occasions when the two get "legitimately" out of sync in Blazor. So when you change the value back to what it was, the Diffing engine sees no difference and thus doesn't send any updates to the Browser DOM. You see a similar situation when building text boxes where you only allow certain input characters.
The only solution I know is to leverage the async behaviour of the Blazor UI event handler:
String.Empty
.Task.Delay
.StateHasChanged
and updates the UI.StateHasChanged
and will update the input to the correct value.@page "/"
<div>
Foo: <select value="@Foo" @onchange="Update">
<option>(none)</option>
<option>First</option>
<option>Second</option>
</select>
</div>
<div>
Value of Foo: @Foo
</div>
@code {
private string Foo {get;set;} = "First";
private async Task Update(ChangeEventArgs args) {
Foo = string.Empty;
await Task.Delay(1);
Foo = "First";
}
}
Upvotes: 4
Reputation: 1450
If I understood your requirement, try this code, it will show selected value of Foo
<select value="@Foo" @onchange="@(e=> Foo=e?.Value?.ToString())">
<option value="(none)">(none)</option>
<option value="First">First</option>
<option value="Second">Second</option>
</select>
Upvotes: -1
Reputation: 8964
The simplest way is to give the element a @key
that you change when you want to force a refresh. This will completely tear down and recreate the element though, so it can be costly.
@page "/"
<select @key="@Forced" value="@Foo" @onchange=@( e => Update(e) )>
<option>(none)</option>
<option>First</option>
<option>Second</option>
</select>
@code {
private string Foo {get;set;} = "First";
private int Forced {get;set;} = 0;
private Task Update(ChangeEventArgs args) {
if (1 == 1){ // placeholder for other logic
Forced++; // doesn't actually change it
}
else
{
Foo = args.Value.ToString() ?? "(none)";
}
return Task.CompletedTask;
}
}
Upvotes: 4