Search code examples
blazorblazor-webassembly

input text value: revert to previous value


I am building a custom Blazor component which @inherits InputBase<string[]>.

Internally that input String[] is parsed and turned into a list of objects.

That list of objects is iterated over and for each item input is generated:

<input type="text" value="@_list[j].Value" @onchange="(e => UpdateItem(j, e))" />

UpdateItem method does several things. For example it updates items in collection and checks if new value is empty string and if it is it removes item from collection.

However, when user adds invalid characters to input text, UpdateItem strips them out and does list[index] = v, but because list[index] == v it does not change underlying Item or List. CurrentValue stays correct, but interface does not update, e.g. input text keeps invalid value which is misleading. If I remove that item and insert it at the same index it still does not work. Removing and adding it does update interface but item is added at the end which is confusing.

I tried to call StateHasChanged() in UpdateItem method and other places but it does not help.

How can I "tell" input value to update itself? Or how can I "mark" value as updated although it is the same in order to trigger interface update.

Maybe I have wrong approach altogether to this problem. I've started with <input @bind="@_list[j].Value"... /> which works great but I need control over binding to validate input and eventually change it. That is why I took the route described.


Solution

  • There are a couple of issues that I think are catching you out.

    First here's a working demo page that I think encapsulates what you are trying to achieve:

    @page "/StringTester"
    <h3>String Test Page</h3>
    
    @foreach (var str in MyStrings)
    {
        <div class="form">
            <input @key="str" class="form-control" value="@str.Value" @onchange="(e) => StringChanged(e, str.Key )" />
        </div>
    }
    
    @code {
    
        SortedList<int, string> MyStrings = new SortedList<int, string> { { 1, "UK" }, { 2, "Australia" }, { 3, "Spain" } };
    
        string[] invalidchars = { "=", "+" };
    
        //  Must be async and Task based for the Yield to work
        async Task StringChanged(ChangeEventArgs e, int key)
        {
            var val = e.Value.ToString();
            if (string.IsNullOrEmpty(val))
                MyStrings.Remove(key);
            else
            {
                var newvalue = InvalidCharStripper(val);
                if (MyStrings[key].Equals(newvalue))
                {
                    MyStrings[key] = val;
                    await Task.Yield();
                }
                MyStrings[key] = newvalue;
            }
        }
    
        //  quick and dirty method to do some input manipulation  - remove invalid chars from the string
        string InvalidCharStripper(string value)
        {
            foreach (var x in invalidchars)
            {
                value = value.Replace(x, string.Empty);
            }
            return value;
        }
    }
    
    1. The "strings are objects but passed by value" issue.

    2. The Blazor Renderer. When you create the input it gets rendered as <input value="Australia">. When the user changes it to "Austalia+", the processing removes the "+" and sets it to "Australia". The display value "Austalia+" only exists in the actual browser DOM, not the Renderer DOM. The renderer still thinks it's "Austalia", so doesn't re-render it.

    I've solved the first issue by setting up the string array as an ordered list. It's now an object and has order.

    The second issue I solved by checking if the new value (with invalid chars removed) is the same as the old value. If it is it, then I set the MyStrings value to the invalid value and Yield control back to the Task scheduler. The event handler now re-renders the component before the final MyStrings[key] = newvalue code block runs. The component is rendered for the final time, with the correct value assigned to the input.

    If you want to know more about the Blazor event process you can read more here