Empresas
Empleos
  • Sobre nosotros
  • Soluciones
    • Publicación de vacantes
      Publica tu vacante y recibe candidatos calificados en 48h.
    • Evaluación de candidatos
      500+ pruebas técnicas y psicológicas, más anti-fraude.
    • Headhunting
      Búsqueda ejecutiva a la medida de principio a fin.
    • Nómina + EOR
      Dispersión de nómina y EOR en más de 15 países de LATAM.
  • Precios
  • Empleos

0

415
Vistas
In Blazor, is there a way to undo invalid user input, without changing the state?

In Blazor, how can I undo invalid user input, without changing the state of the component to trigger a re-render?

Here is a simple Blazor counter example (try it online):

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

@code {
    int count = 1;

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

            // if count hasn't changed here,
            // I want to re-render "A"

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

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

For input element A, I want to replicate the behavior of input element B, but without using the bind-xxx-style data binding attributes.

E.g., when I type 123x inside A, I want it to revert back to 123 automatically, as it happens with B.

I've tried StateHasChanged but it doesn't work, I suppose, because the count property doesn't actually change.

So, basically I need to re-render A to undo invalid user input, even thought the state hasn't changed. How can I do that without the bind-xxx magic?

Sure, bind-xxx is great, but there are cases when a non-standard behavior might be desired, built around a managed event handler like ChangeEvent.


Updated, to compare, here's how I could have done it in React (try it online):

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

Also, here's how I could have done it in Svelte, which I find conceptually very close to Blazor (try it online).

<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/>

Updated, 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 simply use the number format requirement here as an example; I want to be able to undo any arbitrary input like that.

The user experience I want (done via a JS interop hack): https://blazorrepl.telerik.com/wPbvcvvi128Qtzvu03

Surprised this so difficult in Blazor compared to other frameworks, and that I'm unable to use StateHasChanged to simply force a re-render of the component in its current state.

over 4 years ago · Santiago Trujillo
6 Respuestas
Responde la pregunta

0

You will have to use @on{DOM EVENT}-preventDefault.

It is coming from the javascript world and it prevents the default behavior of the button.

In blazor and razor, validation or DDL trigger postback, which means the request is processed by the server and re-renders.

When doing this, you need to make sure your event does not 'bubble' as you are preventing from the event to perform postback.

If you find your event bubbling, meaning element event going up the elements it is nested in, please use:

stopPropagation="true";

for more info:

Suppressing events in blazor

over 4 years ago · Santiago Trujillo Denunciar

0

Below is what I've come up with (try online). I wish ChangeEventArgs.Value worked both ways, but it doesn't. Without it, I can only think of this JS.InvokeVoidAsync hack:

@inject IJSRuntime JS

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

    async Task 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();           

            // using JS interop as a workaround 
            await JS.InvokeVoidAsync("eval",
                $"document.getElementById('{id}').value = Number('{count}')");
        }
    }
}

To be clear, I realize this is a horrible hack (last but not least because it uses eval); I could possibly improve it by using ElementReference and an isolated JS import, but all of that wouldn't be necessary if e.Value = count just worked, like it does with Svelte. I've raised this as a Blazor issue in ASP.NET repo, hopefully it might get some attention.

over 4 years ago · Santiago Trujillo Denunciar

0

From looking at the code, it seems you wanna sanitize user input? E.g. can enter only numbers, date formats etc... I agree it's kinda hard if you wanna use the event handlers manually in this regard at the moment, but it's still possible to validate input using expanded properties and Blazor's binding system.

What you want would look like this: (try it here)

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

@code {
    int _count = 1;
    public string Count 
    {
        get => _count.ToString();
        set 
        {
            if(int.TryParse(value, out var val)) {
                _count = val;
            }
            else {
                if(string.IsNullOrEmpty(value)) {
                    _count = 0;
                }
            }
        }
    }

    void Increment() => _count++;
}
over 4 years ago · Santiago Trujillo Denunciar

0

You can use @on{DOM EVENT}:preventDefault to prevent the default action for an event. For more information look at Microsoft docs: https://docs.microsoft.com/en-us/aspnet/core/blazor/components/event-handling?view=aspnetcore-6.0#prevent-default-actions

UPDATE

An example of using preventDefault

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

@code {
    int count = 1;
    string input = "";

    void Increment() => count++;

    private void KeyHandler(KeyboardEventArgs e)
    {
        //Define your pattern
        var pattern = "abcdefghijklmnopqrstuvwxyz";
        if (!pattern.Contains(e.Key) && e.Key != "Backspace")
        {
            //This is just a sample and needs exception handling
            input = input + e.Key;
            count = int.Parse(input);
        }
        if (e.Key == "Backspace")
        {
            input = input.Remove(input.Length - 1);
            count = int.Parse(input);
        }
    }
}
over 4 years ago · Santiago Trujillo Denunciar

0

This was a really interesting question. I've never used Blazor before, but I had an idea about what might help here, albeit this is a hacky answer too.

I noticed if you changed the element to bind to the count variable, it would update the value when the control lost focus. So I added some code to swap focusing elements. This seems to allow typing non-numeric characters without changing the input field, which is what I think is desired here.

Obviously, not a fantastic solution but thought I'd offer it up in case it helps.

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

@code {
    ElementReference textInput1;
    ElementReference textInput2;
    int count = 1;

    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;
                
            e.Value = count.ToString();

            textInput2.FocusAsync();        
            textInput1.FocusAsync();
        }
    }
}
over 4 years ago · Santiago Trujillo Denunciar

0

So, basically I need to re-render A to undo invalid user input, even thought the state hasn't changed. How can I do that without the bind-xxx magic?

You can force recreating and rerendering of any element/component by changing value in @key directive:

<input @oninput=OnChange value="@count" @key="version" />
void OnChange(ChangeEventArgs e)
{
    var userValue = e.Value?.ToString(); 
    if (int.TryParse(userValue, out var v))
    {
        count = v;
    }
    else 
    {
        version++;
        if (String.IsNullOrWhiteSpace(userValue))
        {
            count = 0;
        }         
   }
}

Notice, that it will rerender the entire element (and it's subtree), not just the attribute.


The problem with your code is, that the BuildRendrerTree method of your component generates exactly the same RenderTree, so the diffing algorithm doesn't find anything to update in the actual dom.

So why the @bind directive works?

Notice the generated BuildRenderTree code:

//A
__builder.OpenElement(6, "input");
__builder.AddAttribute(7, "oninput", Microsoft.AspNetCore.Components.EventCallback.Factory.Create<Microsoft.AspNetCore.Components.ChangeEventArgs>(this, OnChange));
__builder.AddAttribute(8, "value", count);
__builder.CloseElement();

//B
__builder.AddMarkupContent(9, "<br>\r\nB: ");
__builder.OpenElement(10, "input");
__builder.AddAttribute(11, "value", Microsoft.AspNetCore.Components.BindConverter.FormatValue(count));
__builder.AddAttribute(12, "oninput", Microsoft.AspNetCore.Components.EventCallback.Factory.CreateBinder(this, __value => count = __value, count));
__builder.SetUpdatesAttributeName("value");
__builder.CloseElement();

The trick is, that @bind directive adds:

__builder.SetUpdatesAttributeName("value");

You can't do this in markup for EventCallback right now, but there is an open issue for it: https://github.com/dotnet/aspnetcore/issues/17281

However you still can create a Component or RenderFragment and write the __builder code manually.

over 4 years ago · Santiago Trujillo Denunciar
Responde la pregunta
Encuentra empleos remotos

¡Descubre la nueva forma de encontrar empleo!

Top de empleos
Top categorías de empleo
Empresas
Publicar vacante Precios Comercial
Legal
Términos y condiciones Política de privacidad
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomiéndame algunas ofertas
Necesito ayuda