Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bound property not updating upon change

Tags:

c#

.net

blazor

In my Blazor app, I have the following input field in a view:

<input bind="@amount.Display" type="text" />

This is bound to a property defined with the following accessors:

get
{
    return _display;
}
set
{
    var parsed = Decimal.Parse(value);
    _display = parsed.ToString("F2");
}

The real accessor logic is more complicated than this, but I've simplified it above to the extent that I can while still retaining the behavior that's confusing me.

Basically, when the user enters "2", then tabs or clicks out of the input field, I'd like this to automatically be converted to "2.00". What's strange is that this conversion only seems to happen if the user enters a string representing a number that is different than the one already in place. For example, if the input field currently has a value of "1.00" and I enter "2", I correctly end up with "2.00." But if the input field has a value of "2.00" and I enter "2", it just remains "2". It's as though the set accessor is not being called in this latter case, and I can't imagine why.

like image 519
user3678429 Avatar asked Oct 20 '18 05:10

user3678429


2 Answers

The main problem is that Blazor just isn't going to update the textbox if it thinks its value hasn't changed. Part of this problem with your example is that the internal representation of the value (your property) sometimes doesn't jibe with what you see in the textbox (2 vs. 2.00). But as far as Blazor is concerned if the property value doesn't change it's not going to propagate the binding back to the textbox.

I played around with a number of variants of your code (manually "binding" using value and onchange) but ultimately if by the time your code finishes updating the value property and the value is the same as it was before, nothing is going to happen.

The only workaround I can think of is to introduce a really ugly hack by using async. When you do this, the event loop completes after the await (blazor will accept the new value of 2), and blazor will again check to see if anything has changed when it resumes the await call since at that point it's a new round of the event loop. Since the new value will now be 2.00 instead of 2 it refreshes the textbox.

@functions {
    private string value;

    private string Value
    {
        get => value;
        set => SetValue(value);
    }

    private async void SetValue(string value)
    {
        this.value = value;
        await Task.Delay(1);
        this.value = decimal.Parse(value).ToString("F2");
        StateHasChanged();
    }
}

Hopefully someone who knows more about Blazor than me can offer something less atrocious. But it does work. (btw, Task.Delay(0) does not work, presumably due to some optimization where the task is already complete and so doesn't enlist a promise to get out of the event loop.)

Note:
Once Blazor format strings (i.e. format-value="yyyy-MM-dd") accepts numbers, you will probably be able to simplify this by just storing the value as a decimal and using the format string in razor markup directly.

like image 110
Kirk Woll Avatar answered Sep 21 '22 18:09

Kirk Woll


If you want to force UI to re-render, in Blazor I recommend you to use StateHasChanged()

get
{
    return _display;
}
set
{
    var parsed = Decimal.Parse(value);
    _display = parsed.ToString("F2");
    StateHasChanged();
}

The real reason why Blazor does not re-render is: it is try to save rebuilding HTML. Since Blazor knows that you bound property to input it believe that no need to trigger re-rendering after setting property.

The update work:

  1. User enter text '5' and press Tab
  2. Html onchange event was generated
  3. Blazor capture onchange even and call amount.Display = "5";
  4. Done.
like image 22
codevision Avatar answered Sep 23 '22 18:09

codevision