Un EventCallback es un callback de Blazor con el que un componente notifica una acción a quien lo utiliza.
Por ejemplo, ProductCard.razor puede mostrar un producto y ofrecer un botón «Añadir al carrito», mientras que el estado del carrito pertenece al padre o a un servicio. La tarjeta notifica la acción sin modificar ese estado directamente.
El hijo no debe modificar el estado del padre directamente (eso rompería la encapsulación). El hijo debe notificar que algo ha ocurrido.
En C# podríamos usar un evento o un delegado. En los parámetros de componentes Blazor usamos EventCallback, que está integrado con el ciclo de renderizado y admite manejadores asíncronos.
Qué aporta EventCallback
EventCallback es una estructura diseñada específicamente para manejar eventos de interfaz de usuario en Blazor. Funciona de manera similar al patrón Observer: el padre se suscribe a los eventos del hijo y reacciona cuando estos ocurren.
¿Por qué no usar simplemente Action o Func?
Porque EventCallback aporta dos ventajas en este contexto:
- Renderizado automático: Cuando el receptor procesa el callback, Blazor programa su renderizado sin que tengas que llamar normalmente a
StateHasChanged. - Asincronía:
InvokeAsyncpermite esperar manejadores que devuelvenTasky propagar sus errores por el flujo de eventos de Blazor.
Implementación básica sin datos
Vamos a crear un componente hijo ConfirmButton.razor que simplemente avisa cuando el usuario confirma una acción.
El hijo (ConfirmButton.razor)
Definimos un parámetro de tipo EventCallback. Observa que no especificamos tipo genérico porque no vamos a devolver ningún dato, solo un aviso.
<button class="btn btn-success" @onclick="OnConfirm">
✅ Confirmar Operación
</button>
@code {
// Definimos el evento como un parámetro
[Parameter]
public EventCallback OnClick { get; set; }
private async Task OnConfirm()
{
// Invocamos el evento para notificar al padre
if (OnClick.HasDelegate)
{
await OnClick.InvokeAsync();
}
}
}El padre (Parent.razor)
El padre se suscribe al evento pasando el nombre de su propio método manejador.
<h3>Estado: @Status</h3>
<ConfirmButton OnClick="HandleConfirmation" />
@code {
private string Status = "Esperando...";
private void HandleConfirmation()
{
Status = "¡Operación confirmada por el usuario!";
// No hace falta llamar a StateHasChanged(), EventCallback lo hace solo.
}
}Pasar datos con EventCallback<T>
El caso más común es que el hijo quiera enviar información relevante al padre. Por ejemplo, “El usuario ha seleccionado el producto con ID 5”.
Para esto usamos la versión genérica EventCallback
El hijo (ProductCard.razor)
<div class="card">
<h4>@ProductName</h4>
<button @onclick="() => AddToCart(ProductName)">
Comprar
</button>
</div>
@code {
[Parameter] public string ProductName { get; set; } = string.Empty;
// El evento devolverá un string al padre
[Parameter]
public EventCallback<string> OnProductSelected { get; set; }
private async Task AddToCart(string product)
{
await OnProductSelected.InvokeAsync(product);
}
}El padre (Shop.razor)
El método manejador del padre debe aceptar exactamente el mismo tipo de dato que define el EventCallback.
<h3>Carrito: @CartItems.Count productos</h3>
<div class="d-flex">
<ProductCard ProductName="Laptop" OnProductSelected="HandleProductAdd" />
<ProductCard ProductName="Mouse" OnProductSelected="HandleProductAdd" />
</div>
<ul>
@foreach(var item in CartItems)
{
<li>@item</li>
}
</ul>
@code {
private List<string> CartItems = new List<string>();
// El parámetro 'product' viene del hijo
private void HandleProductAdd(string product)
{
CartItems.Add(product);
Console.WriteLine($"Se añadió: {product}");
}
}El hijo no sabe nada sobre CartItems ni sobre cómo funciona el carrito. Solo comunica qué producto se ha seleccionado y el padre decide qué hacer. Así mantenemos los componentes desacoplados.
Diferencias con Action y Func
Veamos por qué un Action no ofrece la misma integración con el renderizador.
// ⛔ MAL (o al menos, problemático)
[Parameter] public Action OnClick { get; set; }
// En el padre:
private void HandleClick()
{
Count++;
// ¡La UI NO se actualizará automáticamente aquí!
// Tendrías que añadir StateHasChanged(); manualmente.
}Al usar EventCallback, Blazor entiende que esa invocación es parte del ciclo de vida de la UI e intercepta la llamada para refrescar la vista al terminar.
Pasar eventos a través de varias capas
A veces tienes una jerarquía profunda: Abuelo -> Padre -> Hijo. Y el Hijo lanza un evento que el Abuelo debe escuchar.
Blazor no tiene “Event Bubbling” automático como el DOM de HTML. Debes pasar el evento explícitamente hacia arriba.
- El
HijoinvocaEventCallback. - El
Padrelo captura, y en su manejador, invoca su propioEventCallback. - El
Abuelolo captura y procesa la lógica.
// En el componente intermedio (Padre)
[Parameter] public EventCallback<string> OnSelected { get; set; }
private async Task HandleChildClick(string item)
{
// Rebotamos el evento hacia arriba
await OnSelected.InvokeAsync(item);
}