Execute async method on button click in blazor
Asked Answered
D

2

45

I created a "Razor Components" project. I am trying to execute an asynchronous method when pressing a button, but could not figure out the syntax yet.

This is my Index.razor:

@page "/"
@inject GenericRepository<Person> PersonRepository 

@foreach (var person in persons)
{
    <button onclick="@(() => Delete(person.Id))">❌</button>
}

@functions 
{
    async void Delete(Guid personId)
    {
        await this.PersonRepository.Delete(personId);
    }
}

When I click the button, nothing happens. I tried various return types (e.g. Task) and stuff, but cannot figure out how to make it work. Please let me know if I need to provide more information.

Every documentation / tutorial only just works with non-async void calls on button click.

Doxia answered 3/4, 2019 at 14:3 Comment(0)
M
67

You need to call the Delete method properly and make it return Task instead of void. The syntax has also changed slightly, you need the @ character before the onclick.

<button @onclick="() => Delete(person.Id)">❌</button>

@functions {

    // ...

    async Task Delete(Guid personId)
    {
        await this.PersonRepository.Delete(personId);
    }
}
Mcgrath answered 3/4, 2019 at 14:13 Comment(8)
I wonder why everyone creates unnecessary async state machines (on anonymous methods). Why not just onclick="@(e => Delete(person.Id))"? Btw: DeleteAsync would not violate naming guidelines.Fat
Only trouble with this is that it does NOT call the method asynchronously. long tasks hang up the main thread :(.Elizabetelizabeth
Do you mind adding some information to this post about the newer advice for async calls? I keep getting this post as the first search result and keep forgetting there's a better way.Amphibrach
@Amphibrach Been a while since I did any Blazor, but I think this advice is still valid. If you are passing in a parameter, you still need to use lambda syntax, though I think you can drop the async from the front if you like.Mcgrath
I'm not clear what bit of code Squibly is refering to, is it the comment above or the code in the answer Would the async () => await Delete(...) block the main thread, or is it the e => Delete(...) that does that?Anaerobe
Answering my own comment... having read (codeproject.com/Articles/5276310/Async-Programming-in-Blazor) It looks like doing the async ()=> is just wrapping one task in another. Its just important that Delete returns a Task.Anaerobe
Update 2024: The recommended event call syntax for async functions with a parameter is now: @onclick="() => Delete(person.Id)"Rivet
@Amphibrach The link you provided is (almost) still valid in current Blazor version. @onclick="Delete" calls the Delete method asynchroneously just like if you had a await Delete(). This works fine because the method has no parameter, but if you had to pass contextual parameters (a frequent use case is buttons created in a loop calling an async method using loop variable, such as a persons list for instance), you should explicitely use the syntax @onclick="() => Delete(person.Id)", just like @Mcgrath did. As long as a Task is returned, the async () => await is unnecessary and redundant.Haggis
P
21

@WoIIe, 1. The purpose of using a lambda expression as a value for the onclick attribute is so that you can pass a value to the Delete method. If you have already defined a person object in your code, you don't have to use a lambda expression. Just do this: onclick = "@Delete", and access person.Id from the Delete method.

  1. Did you click the button a second time? I believe that this code: await this.PersonRepository.Delete(personId); did execute, but you've seen no response on the GUI because the use of void, which is not recommended, requires you to call StateHasChanged(); manually to re-render. Note that StateHasChanged() has already been automatically called once when your method "ended", but since you're returning void and not Task, you should call StateHasChanged() once again to see the changes. But don't do it. See the answer by DavidG how to code properly.

This is also how you can code:

<button onclick="@Delete">Delete Me</button>

@functions {

    Person person = new Person();
    //....
    async Task Delete()
    {
        await this.PersonRepository.Delete(person.Id);
    }
}

More code as per request...

 foreach(var person in people)
    {
        <button onclick="@(async () => await Delete(person.Id))">Delete</button>
    }

@functions {
  // Get a list of People.
  List<Person> People ;

protected override async Task OnParametersSetAsync()
{
    People = await this.PersonRepository.getAll();
}

async Task Delete(Guid personId)
{
     await this.PersonRepository.Delete(personId);
}
}

Note: If you still have not solved your problems, show all your code

Populous answered 3/4, 2019 at 17:36 Comment(1)
Thanks, but the button is inside a foreach(var person in perons) loop. Could you provide some code how you would realize that? Thanks for your clarification in your second point. Ill try that out later :)Vigilant

© 2022 - 2024 — McMap. All rights reserved.