Patrick Szalapski
Patrick Szalapski

Reputation: 9439

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?

Upvotes: 2

Views: 5123

Answers (3)

MrC aka Shaun Curtis
MrC aka Shaun Curtis

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:

  1. Set the value to something different - I normally use String.Empty.
  2. Yield control so the UI can do an update using Task.Delay.
  3. The UI handler calls StateHasChanged and updates the UI.
  4. Set it to the value you want it to be.
  5. The UI handler does it's final 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

Surinder Singh
Surinder Singh

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

Mister Magoo
Mister Magoo

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; 
  }
}

Updated demo

Upvotes: 4

Related Questions