EventCallback handling not working as intended

Summary

This incident documents a Blazor component‑interaction failure where a PaginationBar component raised an EventCallback, but the parent component never re-rendered with the updated page. The callback fired, but the UI did not update because the parent was not receiving or propagating state changes correctly.

Root Cause

The underlying issue was a one‑way data flow mismatch:

  • CurrentPage was passed as a normal parameter, not as a two‑way binding.
  • The PaginationBar invoked OnCurrentPageUpdated, but the parent did not update the value that was being passed back down.
  • Because the parent’s CompanyQueryResult.PageNumber was not refreshed before rendering, the PaginationBar kept receiving the old value, making it appear as if nothing happened.
  • The @onclick:preventDefault on the <li> element also prevented expected bubbling behavior.

Key takeaway: EventCallbacks only notify — they do not automatically update parameters.

Why This Happens in Real Systems

This pattern is extremely common in component‑based UI frameworks:

  • Developers assume EventCallback behaves like two‑way binding, but it does not.
  • Parameters are immutable from the child’s perspective, so updating them inside the child has no effect.
  • Parent components often forget to update the backing model before re-rendering.
  • PreventDefault or nested clickable elements can swallow events or break expected behavior.

Real-World Impact

When this pattern appears in production systems, it causes:

  • Pagination controls that appear dead
  • Filters or search bars that never update
  • Buttons that fire events but do not change UI state
  • Confusing debugging sessions because logs show the callback firing correctly

Example or Code (if necessary and relevant)

Corrected PaginationBar usage with two‑way binding

Corrected parent handler

private async Task HandlePageChange(int newPage)
{
    Filters.PageNumber = newPage;
    CompanyQueryResult = await _service.GetCompaniesAsync(Filters);
}

Corrected child component

private async Task UpdateCurrentPage(int newPage)
{
    await OnCurrentPageUpdated.InvokeAsync(newPage);
}

Recommended: use @onclick only on the <a> element

 UpdateCurrentPage(CurrentPage + 1)">
    

How Senior Engineers Fix It

Experienced engineers recognize the pattern immediately and apply one or more of these solutions:

  • Ensure the parent updates the model that feeds the child parameter
  • Remove preventDefault unless absolutely required
  • Avoid nested clickable elements
  • Use two‑way binding (@bind-Value) when appropriate
  • Verify that the parent’s state is updated before re-rendering
  • Keep pagination logic in the parent, not the child

Why Juniors Miss It

Less experienced developers often struggle with this because:

  • They assume EventCallback magically updates parameters, similar to two‑way binding.
  • They don’t yet understand Blazor’s unidirectional data flow.
  • They focus on the child component’s code, not the parent’s state management.
  • They overlook preventDefault side effects.
  • They expect StateHasChanged() to fix everything, even when the underlying state is unchanged.

Bottom line: The callback fired, but the parent never updated the value being passed back down — a classic Blazor data‑flow pitfall.

Leave a Comment