'How do I tell or allow Blazor to "refresh" an HTML element from its bound value that hasn't changed?
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?
Solution 1:[1]
The problem is with the Renderer and Diffing engine.
The value has changed in 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 see's 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.
- Set the value to something different - I normally use String.Empty2. Yield control so the UI can do an update usingTask.Delay
- The UI handler calls StateHasChangedand updates the UI.
- Set it to the value you want it to be.
- The UI handler does it's final StateHasChangedand 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";
  }
}
Solution 2:[2]
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; 
  }
}
Solution 3:[3]
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> 
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source | 
|---|---|
| Solution 1 | |
| Solution 2 | Mister Magoo | 
| Solution 3 | Surinder Singh | 
