I have a Blazor Server app with many buttons rendered in a for-loop.:
@for (int i = 0; i < _navItems.Count; i++)
{
var localI = i;
<div class="col-3 mb-2">
<button @onclick="async () => await SetCurrentAsync(localI)" class="btn btn-sm">
@(i + 1)
</button>
</div>
}
However, this approach is not recommended by Microsoft Docs here because the delegates specified in @onclick
are recreated each time the component is rendered:
Blazor's recreation of lambda expression delegates for elements or components in a loop can lead to poor performance.
The solution provided in the docs thereafter(and also in the linked GitHub issue is to create a Button
type with an Action
property that holds the delegate:
@foreach (var button in _buttons)
{
<div class="col-3 mb-2">
<button @key="button.Id" @onclick="button.Action" class="btn btn-sm">
@(i + 1)
</button>
</div>
}
@code {
List<Button> _buttons = new();
List<NavItem> _items;
protected override async Task OnInitializedAsync()
{
_items = await GetItemsFromDb();
for(int i = 0; i < _items.Count; i++)
{
var localI = i;
_buttons.Add(new Button
{
Id = item.Id,
Action = () => SetCurrent(localI);
});
}
}
class Button
{
public int Id { get; set; }
public Action Action { get; set; }
}
}
Now, the @onclick
references Button.Action
and solves the delegate recreation problem.
It is all fun and games until SetCurrent
is not async.
Action
will have to be changed to Func<Task>
and buttons will have to be added using an async lambda expression:
_buttons.Add(new Button
{
Id = item.Id,
Action = async () => await SetCurrentAsync(localI);
});
And I still have to do:
@onclick="async() => await button.Action"
which would again recreate the delegates. How exactly can I do this for async methods?