2

I am new to Blazor (and web dev in general). I was following along with Microsoft's Blazor web app Todo List tutorial, and after finishing said tutorial I wanted to go further and add buttons beside each list element to remove them from the list. This is the code I wrote to accomplish that:

@page "/todo"

<h1>Todo (@todos.Count(todo => !todo.IsDone))</h1>

<ul>
    @foreach (var todo in todos)
    {
        <li>
            <input type="checkbox" @bind="todo.IsDone" />
            <input @bind="todo.Title" />
            <button @onclick="RemoveTodo(todo)">Remove</button>
        </li>
    }
</ul>

<input placeholder="Something to do" @bind="newTodo" />
<button @onclick="AddTodo">Add todo</button>

@code {
    private List<TodoItem> todos = new();
    private string newTodo;
    
    private void AddTodo()
    {
        if (!string.IsNullOrWhiteSpace(newTodo))
        {
            todos.Add(new TodoItem { Title = newTodo });
            newTodo = string.Empty;
        }
    }
    
    private void RemoveTodo(TodoItem item)
    {
        if (item != null)
        {
            todos.Remove(item);
        }
    }
}

I thought I could just copy the syntax from <button @onclick="AddTodo">Add todo</button> to add this button, but that introduces a bug. As I found through this Stack Overflow answer, in order to fix this bug (and allow the app to build at all), I must change:

<button @onclick="RemoveTodo(todo)">Remove</button>

to include a lambda function like so:

<button @onclick="() => RemoveTodo(todo)">Remove</button>

I know that this change works, because I tested it and the app behaves as I intended it to! But I want to why this change works.

I found this additional Stack Overflow question, wherein the chosen answer explains that in order to pass values to methods called by @onclick in the above manner, one must use a lambda expression. The answer says that using @onclick will cause the compiler to create an EventCallback object to handle the code I provide to @onclick.

However, I still do not understand why my original code does not work. I assume that the delegate being produced by the EventCallback object cannot execute properly when a value is being passed to the function it is executing. The second question indicated that invoking via a lambda function produced a different kind of delegate, which could resolve the value passed to the function.

Is my understanding of what is happening close to the truth? Why do I need to package functions inside lambda functions in this way, but only when said functions are being passed values?

FlintTD
  • 23
  • 5
  • The return type of `RemoveTodo` is `void`. When assigning to the `@onclick` property, you have to provide a value of the correct type, which is a delegate in this case. – John Wu May 30 '21 at 06:56

2 Answers2

5

It is about C# syntax. An eventhandler must be a delegate (a reference to a method).

The shortest way to show the error:

<button @onclick="AddTodo">Add todo</button>         // ok
<button @onclick="AddTodo()">Add todo</button>       // error
<button @onclick="() => AddTodo()">Add todo</button> // ok

because, in plain C#:

delegate x = AddTodo;    // ok, a methodname w/o () means "address off"
delegate x = AddTodo();  // error, void is not a delegate
delegate x = () => AddTodo();  // ok, a lambda is a delegate

We usually prefer the first shorter version. But as soon as you have an argument you need the long form.

H H
  • 263,252
  • 30
  • 330
  • 514
  • To be slightly annoying, you should be using discards `_` if you are not going to use the event args variable: `` :) – Cory Podojil May 31 '21 at 20:29
  • Well... OnClick has (can have) an EventArgs but Blazor makes that optional. So I don't see any benefit in using a discard here. It is already solved in the .g.cs file. – H H May 31 '21 at 20:43
1

If you hover over @onclick (or other @events) in any control, you will get information about the function that expects by default: a string or delegate value, and if it's a delegate, it should be of the type MouseEventArgs (in this case)

I wouldn't speculate as to why they did things one way or another, but the docs show examples for the various ways to handle events in Blazor:

https://learn.microsoft.com/en-us/aspnet/core/blazor/components/event-handling?view=aspnetcore-5.0

IMO, the official docs are usually very good. I usually google "MSDN BLAZOR ____" to find what I want.

Bennyboy1973
  • 3,413
  • 2
  • 11
  • 16