Skip to content

Blazor WebAssembly: make ChangeEventArgs.Value work both ways for the scope of the event handler  #38520

Closed as not planned
@noseratio

Description

@noseratio

Is your feature request related to a problem? Please describe.

I'd like to be able to undo invalid user input without changing the state of the component or using the bind:value Blazor data binding feature.

I'm trying to do that inside oninput event handler like below (online repl).

I've found that ChangeEventArgs.Value, while being a get/set property, only works one way, from JS to DotNet. More so, StateHasChanged doesn't trigger rerendering in this case. Thus, I had to resort to JS interop to revert the changes inside the <input> box A.

(To be clear, I realize this is a horrible hack; it could possible be improved by using ElementReference and an isolated JS import, but it all wouldn't be necessary if we could just do e.Value = count below).

<label>Count:</label>
<button @onclick=Increment>@count times!</button><br>
A: <input @oninput=OnChange value="@count" id="@id"><br>
B: <input @bind-value=count @bind-value:event="oninput">

@code {
    int count = 1;
    string id = Guid.NewGuid().ToString("N");

    void Increment() => count++;

    void OnChange(ChangeEventArgs e)
    {
        var userValue = e.Value?.ToString(); 
        if (int.TryParse(userValue, out var v))
        {
            count = v;
        }
        else 
        {
            if (String.IsNullOrWhiteSpace(userValue))
            {
                count = 0;
            }

            // this doesn't work
            e.Value = count.ToString();

            // this doesn't work either (no rererendering):
            StateHasChanged();           
       }
    }
}

Describe the solution you'd like

Make ChangeEventArgs.Value work both ways for the scope of the event handler. This would allow for custom input validation logic outside standard bind-xxx Blazor data binding attributes.

For example, I can do this in Svelte (which I find conceptually very close to Blazor):

<script>
  let count = 1;
  const handleClick = () => count++;
  const handleChange = e => {
    const userValue = e.target.value;
    let newValue = userValue? parseInt(userValue): 0;
    if (isNaN(newValue)) newValue = count;
    if (newValue === count)
      e.target.value = count; // undo user input
    else
      count = newValue;	
  };	
</script>

Count: <button on:click={handleClick}>{count}</button><br/>
A: <input value={count} on:input={handleChange}/><br/>

And here it is in React:

function App() {
  let [count, setCount] = useState(1);
  const handleClick = () => setCount((count) => count + 1);
  const handleChange = (e) => {
    const userValue = e.target.value;
    let newValue = userValue ? parseInt(userValue) : 0;
    if (isNaN(newValue)) newValue = count;
    // re-render even when count hasn't changed
    setCount(newValue); 
  };
  return (
    <>
      Count: <button onClick={handleClick}>{count}</button><br/>
      A: <input value={count} onInput={handleChange}/><br/>
    </>
  );
}

Additional context

https://stackoverflow.com/q/70016908/1768303

To clarify, I simply want to undo whatever I consider an invalid input, retrospectively after it has happened (by handling the change event), without mutating the component's state itself (counter here).

That is, without Blazor-specific two-way data binding, HTML native type=number or pattern matching attributes. I use the number format requirement here as an example; I want to be able to undo any arbitrary input like that.

I might be missing something, but I find this surprisingly difficult to do in Blazor, compared to other frameworks. I'd like to be able to use StateHasChanged to simply re-render the component in its current state.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-blazorIncludes: Blazor, Razor Componentsfeature-renderingFeatures dealing with how blazor renders components

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions