skip to Main Content

this is my first time with Blazor, so apologies if this sounds stupid. Here is some Blazor code I wrote:

<ul>
@foreach (var tag in tags)
    {
        <li>
            @tag
            <button class="btn btn-primary" @onclick="() => remove(tag)">remove</button>
        </li>
    }
</ul>

<PageTitle>Home</PageTitle>

<h1>Tags</h1>

<input placeholder="Enter tag" @bind=@tag @onkeydown="enter"/>
<button class="btn btn-primary" @onclick="addTag">Add</button>
<button class="btn btn-primary" @onclick="clear">Clear</button>

@code {
    private string tag;

    private List<string> tags = new List<string>();

    private void addTag()
    {
        if (tags.Count() < 5)
        {
            tags.Add(tag);
            tag = string.Empty;
        }
    }
    private void enter(KeyboardEventArgs e)
    {
        if (e.Code == "Enter")
        {
            addTag();
        }
    }
    private void clear()
    {
        tags.Clear();
    }
    private void remove(string tag)
    {
        tags.Remove(tag);
    }
}

For some reason that I cannot figure out, I expected that both when the Add button is clicked, or the enter key is pressed, the behavior should be the same, i.e, the input entered in the text box will get stored in @tag, and added to the tags list. This is indeed what happens when the add button is clicked, but when the Enter key is pressed, the first tag in the list of tags is always an empty string, and then on, it is off by one. So for example:

If I put mango in the input box and hit enter, the first <li> item to pop up on my screen is "".
If I then put apple in the input box and hit enter, the next <li> item to pop up on my screen is mango.

It is off by one, and for the life of me, I cannot figure out why.

2

Answers


  1. The problem is by default the @bind attribute updates the value when the change event is fired on the input control. When you press enter the input box, the onkeydown event will be fired before the change event, so the value of the tag field doesn’t change. To keep the value up-to-date you can do:

    <input placeholder="Enter tag" @bind=@tag @bind:event="oninput" @onkeydown="enter"/>
    

    You can also try keyup event, because it is fired after the change event:

    <input placeholder="Enter tag" @bind=@tag @onkeyup="enter"/>
    

    ASP.NET Core Blazor data binding

    Login or Signup to reply.
  2. Your problem happens because of the order in which the events execute.

    If you add the following code you will see that tag is being set.

    <div class="alert alert-info">
        Tag: @tag
    </div>
    

    In your current setup:

    1. The input is wired up the onchanged event (wired up by the @bind.)
    2. It updates tag when either the input loses focus (you click somewhere else) or your click Enter.

    However the onkeydown event occurs when you press enter i.e. before the onchanged event. At this point tag contains the old value, it hasn’t been updated yet.

    You can change the event sequence by manually wiring up the input and triggering tag updates on the oninput event rather that on onchange. Note I’ve also moved OnEnter to the onkeyup event to ensure it’s called last (you can never be sure of how differtent browsers implement these events).

    Here’s your input:

    Version 1 – Net6.0 or earlier

    <input placeholder="Enter tag" value="@this.tag" @onkeyup=this.OnEnter @oninput=this.OnInput />
    

    Version 2 – Net7.0

    <input placeholder="Enter tag" @bind:get=this.tag @bind:event="oninput" @bind:set=this.OnSet @onkeyup=this.OnEnter />
    

    And the UI event handlers.

        private void OnEnter(KeyboardEventArgs e)
        {
            if (e.Code == "Enter")
                addTag();
        }
    
        private void OnInput(ChangeEventArgs e)
            => tag = e.Value?.ToString() ?? string.Empty;
            
        private void OnSet(string value)
            => tag = value;
    
    

    The events now trigger in the correct order.

    Here’s my full demo page:

    @page "/"
    
    <PageTitle>Index</PageTitle>
    <ul>
        @foreach (var tag in tags)
        {
            <li>
                @tag
                <button class="btn btn-primary" @onclick="() => remove(tag)">remove</button>
            </li>
        }
    </ul>
    
    <PageTitle>Home</PageTitle>
    
    <h1>Tags</h1>
    
    <input placeholder="Enter tag" value="@this.tag" @onkeyup=this.OnEnter @oninput=this.OnInput />
    <button class="btn btn-primary" @onclick="addTag">Add</button>
    <button class="btn btn-primary" @onclick="clear">Clear</button>
    
    <div>
        <input placeholder="Enter tag" @bind:get=this.tag @bind:event="oninput" @bind:set=this.OnSet @onkeyup=this.OnEnter />
    </div>
    
    <div class="alert alert-info">
        Tag: @tag
    </div>
    
    @code {
        private string tag;
    
        private List<string> tags = new List<string>();
    
        private void addTag()
        {
            if (tags.Count() < 5)
            {
                tags.Add(tag);
                tag = string.Empty;
            }
        }
    
        private void OnEnter(KeyboardEventArgs e)
        {
            if (e.Code == "Enter")
                addTag();
        }
    
        private void OnInput(ChangeEventArgs e)
            => tag = e.Value?.ToString() ?? string.Empty;
    
    
        private void OnSet(string value)
            => tag = value;
    
        private void clear()
        {
            tags.Clear();
        }
        private void remove(string tag)
        {
            tags.Remove(tag);
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search