DEV Community

Cover image for Mastering Blazor Component Communication: EventCallback, CascadingParameter, and @ref Explained
Spyros Ponaris
Spyros Ponaris

Posted on • Edited on

6 1

Mastering Blazor Component Communication: EventCallback, CascadingParameter, and @ref Explained

Blazor brings modern component-based architecture to .NET developers, enabling rich, interactive UIs using Razor components. But like any component model, communication between components is key.

In this article, we'll explore the three core techniques for communication in Blazor:

  1. EventCallback – for child-to-parent interaction
  2. CascadingParameter – for shared context across components
  3. @ref – for directly accessing methods and properties of child components

These techniques apply whether you're using Blazor WebAssembly or Blazor Server.


🧩 1. EventCallback – Raise Events from Child to Parent

This is the recommended way for a child component to notify its parent of a user action.

✅ Example: Product Selection

Child Component: ProductList.razor

@code {
    [Parameter] public EventCallback<Product> OnProductSelected { get; set; }

    private List<Product> products = new() {
        new() { Id = 1, Name = "Laptop" },
        new() { Id = 2, Name = "Phone" }
    };
}
<ul>
    @foreach (var p in products)
    {
        <li @onclick="() => OnProductSelected.InvokeAsync(p)">
            @p.Name
        </li>
    }
</ul>
Enter fullscreen mode Exit fullscreen mode

Parent Page

<ProductList OnProductSelected="HandleProductSelected" />

@code {
    private Product? selectedProduct;
    private void HandleProductSelected(Product product)
    {
        selectedProduct = product;
    }
}
Enter fullscreen mode Exit fullscreen mode

✅ Use this when the child needs to notify the parent of an action (like a click or submit).

🌐 2. CascadingParameter – Share Data Down the Tree

Use CascadingParameter when multiple nested components need access to shared state or context.

✅ Example: Shared Model

Parent Component

<CascadingValue Value="currentProduct">
    <ProductDetails />
</CascadingValue>

@code {
    private Product currentProduct = new() { Name = "Tablet", Description = "Shared model" };
}
Enter fullscreen mode Exit fullscreen mode

Child Component: ProductDetails.razor

@code {
    [CascadingParameter] public Product? Product { get; set; }
}

@if (Product != null)
{
    <p>@Product.Name - @Product.Description</p>
}
Enter fullscreen mode Exit fullscreen mode

📞 3. @ref – Call Methods on Child Components

Using @ref, the parent component can hold a reference to the child and call its public methods or access properties directly.

✅ Example: Editable Product List with Change Tracking

Child Component: ProductEditor.razor

@code {
    [Parameter] public List<Product>? Products { get; set; }

    private HashSet<int> changedIds = new();

    public List<Product> GetChangedProducts() =>
        Products?.Where(p => changedIds.Contains(p.Id)).ToList() ?? new();

    public void MarkAsChanged(Product product)
    {
        changedIds.Add(product.Id);
        StateHasChanged();
    }

    private void OnNameChanged(Product p, string val)
    {
        p.Name = val;
        MarkAsChanged(p);
    }
}

Enter fullscreen mode Exit fullscreen mode

Parent Page

<ProductEditor Products="products" @ref="editorRef" />

<button @onclick="GetChanges">Get Changed</button>

@code {
    private ProductEditor? editorRef;
    private List<Product> products = new() {
        new() { Id = 1, Name = "Phone" },
        new() { Id = 2, Name = "Tablet" }
    };

    private void GetChanges()
    {
        var changed = editorRef?.GetChangedProducts();
    }
}
Enter fullscreen mode Exit fullscreen mode

✅ Use this when the parent needs to trigger actions or pull state from the child.

🆚 When to Use What

Pattern Direction Use Case
EventCallback Child → Parent Notify parent of events (e.g., selection, form submit)
CascadingParameter Parent → Child Share context or models across many levels
@ref Parent → Child Imperatively access or control child behavior

⚠️ Important Considerations

No Automatic UI Refresh:

Blazor does not automatically call StateHasChanged() when you use @ref to change a child component's state.
If your changes affect what should be rendered, you must manually trigger a UI update:

_someRef.DoSomething();
StateHasChanged(); // manually trigger re-render if needed
Enter fullscreen mode Exit fullscreen mode

Initialization Timing:

You can't use the reference during OnInitialized — it will be null. Use OnAfterRenderAsync or wait for an event like a button click.

Lifecycle Coupling:

@ref tightly couples the parent to the child. Overuse can lead to fragile code. Prefer EventCallback and Parameter binding where possible.

🧠 Best Practice :

Use @ref sparingly and intentionally, typically when:

You need to call an imperative method (e.g., FocusAsync(), Validate()).

There's no clean way to achieve the behavior through data binding or parameters.

You're building custom input components that need internal coordination.

🧠 Final Thoughts :

Blazor gives you multiple tools to handle component communication. Choosing the right one depends on your direction of data flow, how many components are involved, and whether you're building reactive UIs or invoking imperative logic.

✅ Bonus: Combine Them!

You can (and often should) use multiple patterns together:

  • Use CascadingParameter for shared state
  • Use EventCallback to notify the parent
  • Use @ref when you need fine-grained control

🛠 Project Repo

Check out the full demo on GitHub:
👉 BlazorComponentComms

Tiugo image

Modular, Fast, and Built for Developers

CKEditor 5 gives you full control over your editing experience. A modular architecture means you get high performance, fewer re-renders and a setup that scales with your needs.

Start now

Top comments (0)