blazor-eventcallback-comunicacion-hijos-padres

EventCallback in Blazor: Communicating Children with Parents

  • 4 min

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:

  1. Automatic Rendering: When the receiver processes the callback, Blazor schedules its rendering without you normally needing to call StateHasChanged.
  2. Asynchrony: InvokeAsync allows waiting for handlers that return Task and 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();
        }
    }
}
Copied!

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.
    }
}
Copied!

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. Let’s improve our cart example.

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);
    }
}
Copied!

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}");
    }
}
Copied!

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();.
}
Copied!

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.

  1. The Child invokes EventCallback.
  2. The Parent captures it, and in its handler, invokes its own EventCallback.
  3. The Grandparent captures 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);
}
Copied!