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:
-
EventCallback
– for child-to-parent interaction -
CascadingParameter
– for shared context across components -
@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>
Parent Page
<ProductList OnProductSelected="HandleProductSelected" />
@code {
private Product? selectedProduct;
private void HandleProductSelected(Product product)
{
selectedProduct = product;
}
}
✅ 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" };
}
Child Component: ProductDetails.razor
@code {
[CascadingParameter] public Product? Product { get; set; }
}
@if (Product != null)
{
<p>@Product.Name - @Product.Description</p>
}
📞 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);
}
}
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();
}
}
✅ 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
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
Top comments (0)