I want to set the active panel index based on a query parameter value
Problem Description
When navigating to a Blazor page containing MudTabs with a query parameter specifying the active tab index (e.g., ?tab=1), the application throws an ArgumentOutOfRangeException during the component's first render cycle.
Exception Details
System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index')
at System.Collections.Generic.List`1.get_Item(Int32 index)
at MudBlazor.MudTabs.OnAfterRenderAsync(Boolean firstRender)
Environment
- MudBlazor Version: 7.15.0
- Blazor Hosting Model: Server
- .NET Version: 9.0
Minimal Reproducible Example
Page Component (.razor)
@page "/example/{Id:long}"
@using MudBlazor
<MudTabs ActivePanelIndexChanged="@OnActivePanelChanged"
@ref="_tabs">
<MudTabPanel Text="Details">
<p>Details content</p>
</MudTabPanel>
<MudTabPanel Text="Tab 2" Disabled="@(!_enableTab2)">
<p>Tab 2 content</p>
</MudTabPanel>
<MudTabPanel Text="Tab 3" Disabled="@(!_enableTab3)">
<p>Tab 3 content</p>
</MudTabPanel>
</MudTabs>
Code Behind (.razor.cs)
public partial class Example : ComponentBase
{
[Parameter] public long Id { get; set; }
[Parameter, SupplyParameterFromQuery(Name = "tab")]
public int? TabFromQuery { get; set; }
private MudBlazor.MudTabs? _tabs;
private int? _activePanelIndex;
private int? _pendingIndexFromQuery;
private bool _tabsInitialized;
private bool _enableTab2 = false;
private bool _enableTab3 = false;
protected override Task OnParametersSetAsync()
{
// Store query parameter for later use
if (TabFromQuery is int i && i >= 0)
_pendingIndexFromQuery = i;
return Task.CompletedTask;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
// Try to activate the tab from query parameter
if (_tabs?.Panels.Count > 0 && !_tabsInitialized)
{
_tabsInitialized = true;
if (_pendingIndexFromQuery.HasValue)
{
var count = _tabs.Panels.Count;
var index = Math.Min(_pendingIndexFromQuery.Value, count - 1);
_tabs.ActivatePanel(index);
_activePanelIndex = index;
_pendingIndexFromQuery = null;
}
else
{
_activePanelIndex = 0;
}
}
}
private void OnActivePanelChanged(int index)
{
_activePanelIndex = index;
}
}
Steps to Reproduce
- Create a page with MudTabs and dynamic/disabled panels
- Navigate to the page with a query parameter:
/example/123?tab=1 - Exception is thrown in
MudTabs.OnAfterRenderAsync()
Root Cause
The exception occurs during the MudTabs component lifecycle:
- OnParametersSetAsync: Query parameter
tab=1is captured - First Render: MudTabs starts rendering,
Panelscollection is being populated - OnAfterRenderAsync(firstRender: true): MudTabs internally tries to access
Panels[ActivePanelIndex] - Problem: At this moment,
Panelscollection is not yet fully initialized or is empty - Result:
ArgumentOutOfRangeExceptionwhen accessing the index
Expected Behavior
- MudTabs should handle cases where
ActivePanelIndexis set before panels are fully initialized - Should gracefully default to index 0 or wait for panels to be ready
- No exception should occur when navigating with query parameters
Workarounds Attempted
All of these still produce the exception:
❌ Attempt 1: Direct ActivePanelIndex Binding
<MudTabs ActivePanelIndex="@_activePanelIndex" ...>
Result: Same exception during first render
❌ Attempt 2: Using ActivatePanel in OnAfterRenderAsync
if (_tabs?.Panels.Count > 0)
{
_tabs.ActivatePanel(requestedIndex);
}
Result: Exception still occurs before our code executes
❌ Attempt 3: Delaying with StateHasChanged
protected override async Task OnAfterRenderAsync(bool firstRender)
{
StateHasChanged();
await Task.Delay(100);
_tabs?.ActivatePanel(index);
}
Result: Exception occurs before delay
❌ Attempt 4: Setting in OnInitializedAsync
protected override async Task OnInitializedAsync()
{
if (_pendingIndexFromQuery.HasValue)
_tabs?.ActivatePanel(_pendingIndexFromQuery.Value);
}
Result: _tabs is null, panels don't exist yet