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:
CurrentPagewas 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.PageNumberwas not refreshed before rendering, the PaginationBar kept receiving the old value, making it appear as if nothing happened. - The
@onclick:preventDefaulton 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.