fix(ui): EventListShell — reload on Filter swap + harden refresh-timer (HIGH+MED)
* Filter changes from the parent (page-state singleton swap) now trigger LoadAsync via OnParametersSetAsync once the component has rendered. Previously the reload happened only on first render, so navigating back to a list page with a different sport/date filter showed the prior data. * Refresh-timer Elapsed handler is hoisted to a named async-void method with try/catch around InvokeAsync. An unhandled exception on the timer thread used to crash WebView2; now it's logged and swallowed. * Overlapping ticks are skipped via a _loading short-circuit so a slow Loader doesn't stack up cancelled-superseded loads.
This commit is contained in:
@@ -317,6 +317,11 @@
|
|||||||
[Parameter] public IReadOnlyList<string>? AvailableCountries { get; set; }
|
[Parameter] public IReadOnlyList<string>? AvailableCountries { get; set; }
|
||||||
[Parameter] public bool LiveMode { get; set; }
|
[Parameter] public bool LiveMode { get; set; }
|
||||||
[Parameter] public int AutoRefreshSeconds { get; set; } = 30;
|
[Parameter] public int AutoRefreshSeconds { get; set; } = 30;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Legacy hint from parent pages that the source data is unseeded;
|
||||||
|
/// currently advisory only — the empty-state copy is unconditional.
|
||||||
|
/// </summary>
|
||||||
[Parameter] public bool Stale { get; set; }
|
[Parameter] public bool Stale { get; set; }
|
||||||
|
|
||||||
private EventBrowsingState.PageFilter _filter = default!;
|
private EventBrowsingState.PageFilter _filter = default!;
|
||||||
@@ -326,15 +331,24 @@
|
|||||||
private List<EventListItem> _rows = new();
|
private List<EventListItem> _rows = new();
|
||||||
private DateTimeOffset? _lastLoadedAt;
|
private DateTimeOffset? _lastLoadedAt;
|
||||||
private bool _loading;
|
private bool _loading;
|
||||||
|
private bool _firstRendered;
|
||||||
private System.Timers.Timer? _refreshTimer;
|
private System.Timers.Timer? _refreshTimer;
|
||||||
private readonly Dictionary<string, RatesSnap> _previousRates = new(StringComparer.Ordinal);
|
private readonly Dictionary<string, RatesSnap> _previousRates = new(StringComparer.Ordinal);
|
||||||
|
|
||||||
protected override void OnParametersSet()
|
protected override async Task OnParametersSetAsync()
|
||||||
{
|
{
|
||||||
|
// Reload when the parent swaps the Filter parameter for a different
|
||||||
|
// instance — page state singletons hand us a new record on every
|
||||||
|
// change, so reference inequality is the right trigger. The first
|
||||||
|
// render still kicks off via OnAfterRenderAsync; this branch covers
|
||||||
|
// subsequent parent-driven filter swaps (e.g. nav between pre-match
|
||||||
|
// and live, or restoring from EventBrowsingState).
|
||||||
if (!ReferenceEquals(_filter, Filter))
|
if (!ReferenceEquals(_filter, Filter))
|
||||||
{
|
{
|
||||||
_filter = Filter;
|
_filter = Filter;
|
||||||
_searchInput = Filter.SearchTerm;
|
_searchInput = Filter.SearchTerm;
|
||||||
|
if (_firstRendered)
|
||||||
|
await LoadAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,6 +356,7 @@
|
|||||||
{
|
{
|
||||||
if (firstRender)
|
if (firstRender)
|
||||||
{
|
{
|
||||||
|
_firstRendered = true;
|
||||||
await LoadAsync();
|
await LoadAsync();
|
||||||
if (LiveMode) StartTimer();
|
if (LiveMode) StartTimer();
|
||||||
}
|
}
|
||||||
@@ -352,11 +367,27 @@
|
|||||||
_refreshTimer?.Dispose();
|
_refreshTimer?.Dispose();
|
||||||
var interval = Math.Max(5, AutoRefreshSeconds) * 1000.0;
|
var interval = Math.Max(5, AutoRefreshSeconds) * 1000.0;
|
||||||
_refreshTimer = new System.Timers.Timer(interval) { AutoReset = true };
|
_refreshTimer = new System.Timers.Timer(interval) { AutoReset = true };
|
||||||
_refreshTimer.Elapsed += async (_, _) =>
|
_refreshTimer.Elapsed += OnRefreshTimerElapsed;
|
||||||
|
_refreshTimer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnRefreshTimerElapsed(object? sender, System.Timers.ElapsedEventArgs e)
|
||||||
|
{
|
||||||
|
// async void here is unavoidable — System.Timers.Timer.Elapsed is an
|
||||||
|
// event with a void-returning signature. We MUST catch every exception
|
||||||
|
// ourselves; an unhandled throw on the threadpool would tear down the
|
||||||
|
// WebView2 process. Skip overlapping ticks so a slow Loader doesn't
|
||||||
|
// stack up cancelled-superseded loads on the UI thread.
|
||||||
|
if (_loading) return;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
await InvokeAsync(LoadAsync);
|
await InvokeAsync(LoadAsync);
|
||||||
};
|
}
|
||||||
_refreshTimer.Start();
|
catch
|
||||||
|
{
|
||||||
|
// Swallowed — LoadAsync already handles its own errors; this catch
|
||||||
|
// is the last line of defense for InvokeAsync itself.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadAsync()
|
private async Task LoadAsync()
|
||||||
|
|||||||
Reference in New Issue
Block a user