An EventCallback is a Blazor callback with which a component notifies an action to whoever uses it.
For example, ProductCard.razor can display a product and offer an “Add to Cart” button, while the cart state belongs to the parent or a service. The card notifies the action without modifying that state directly.
The child should not directly modify the parent’s state (that would break encapsulation). The child should notify that something has occurred.
In C# we could use an event or a delegate. In Blazor component parameters we use EventCallback, which is integrated with the rendering cycle and supports asynchronous handlers.
What EventCallback Brings
EventCallback is a structure specifically designed to handle user interface events in Blazor. It works similarly to the Observer pattern: the parent subscribes to the child’s events and reacts when they occur.
Why not simply use Action or Func?
Because EventCallback provides two advantages in this context:
- Automatic Rendering: When the receiver processes the callback, Blazor schedules its rendering without you normally needing to call
StateHasChanged. - Asynchrony:
InvokeAsyncallows waiting for handlers that returnTaskand propagates their errors through the Blazor event flow.
Basic Implementation Without Data
Let’s create a child component ConfirmButton.razor that simply notifies when the user confirms an action.
The Child (ConfirmButton.razor)
We define a parameter of type EventCallback. Notice we don’t specify a generic type because we aren’t going to return any data, just a notification.
<button class="btn btn-success" @onclick="OnConfirm">
✅ Confirm Operation
</button>
@code {
// Define the event as a parameter
[Parameter]
public EventCallback OnClick { get; set; }
private async Task OnConfirm()
{
// Invoke the event to notify the parent
if (OnClick.HasDelegate)
{
await OnClick.InvokeAsync();
}
}
}The Parent (Parent.razor)
The parent subscribes to the event by passing the name of its own handler method.
<h3>Status: @Status</h3>
<ConfirmButton OnClick="HandleConfirmation" />
@code {
private string Status = "Waiting...";
private void HandleConfirmation()
{
Status = "Operation confirmed by the user!";
// No need to call StateHasChanged(), EventCallback does it automatically.
}
}Passing Data with EventCallback<T>
The most common case is that the child wants to send relevant information to the parent. For example, “The user has selected the product with ID 5”.
For this we use the generic version EventCallback
The Child (ProductCard.razor)
<div class="card">
<h4>@ProductName</h4>
<button @onclick="() => AddToCart(ProductName)">
Buy
</button>
</div>
@code {
[Parameter] public string ProductName { get; set; } = string.Empty;
// The event will return a string to the parent
[Parameter]
public EventCallback<string> OnProductSelected { get; set; }
private async Task AddToCart(string product)
{
await OnProductSelected.InvokeAsync(product);
}
}The Parent (Shop.razor)
The parent’s handler method must accept exactly the same data type defined by the EventCallback.
<h3>Cart: @CartItems.Count products</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>();
// The 'product' parameter comes from the child
private void HandleProductAdd(string product)
{
CartItems.Add(product);
Console.WriteLine($"Added: {product}");
}
}The child knows nothing about CartItems or how the cart works. It only communicates which product has been selected, and the parent decides what to do. This way we keep components decoupled.
Differences with Action and Func
Let’s see why an Action doesn’t offer the same integration with the renderer.
// ⛔ BAD (or at least, problematic)
[Parameter] public Action OnClick { get; set; }
// In the parent:
private void HandleClick()
{
Count++;
// The UI will NOT update automatically here!
// You would have to manually add StateHasChanged();.
}By using EventCallback, Blazor understands that this invocation is part of the UI lifecycle and intercepts the call to refresh the view when it finishes.
Passing Events Through Multiple Layers
Sometimes you have a deep hierarchy: Grandparent -> Parent -> Child. And the Child fires an event that the Grandparent needs to listen to.
Blazor doesn’t have automatic “Event Bubbling” like the HTML DOM. You must pass the event explicitly upwards.
- The
ChildinvokesEventCallback. - The
Parentcaptures it, and in its handler, invokes its ownEventCallback. - The
Grandparentcaptures it and processes the logic.
// In the intermediate component (Parent)
[Parameter] public EventCallback<string> OnSelected { get; set; }
private async Task HandleChildClick(string item)
{
// Bounce the event upwards
await OnSelected.InvokeAsync(item);
}