1

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

  1. Create a page with MudTabs and dynamic/disabled panels
  2. Navigate to the page with a query parameter: /example/123?tab=1
  3. Exception is thrown in MudTabs.OnAfterRenderAsync()

Root Cause

The exception occurs during the MudTabs component lifecycle:

  1. OnParametersSetAsync: Query parameter tab=1 is captured
  2. First Render: MudTabs starts rendering, Panels collection is being populated
  3. OnAfterRenderAsync(firstRender: true): MudTabs internally tries to access Panels[ActivePanelIndex]
  4. Problem: At this moment, Panels collection is not yet fully initialized or is empty
  5. Result: ArgumentOutOfRangeException when accessing the index

Expected Behavior

  • MudTabs should handle cases where ActivePanelIndex is 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

4
  • what is your question? ... please edit your post Commented Nov 10 at 5:35
  • Please clarify your specific problem or provide additional details to highlight exactly what you need. As it's currently written, it's hard to tell exactly what you're asking. Commented Nov 10 at 5:35
  • updated: I want to set the active panel index based on a query parameter value Commented Nov 10 at 5:51
  • 1
    A question normally includes a question mark. Currently you posted a problem statement. We also don't really need failed attempts at answering your question, while a description of what you tried is helpful, showing incorrect code just pollutes your question. Commented Nov 10 at 11:47

1 Answer 1

1

MudTabs fires its internal OnAfterRenderAsync before all MudTabPanel components are registered.
If you set ActivePanelIndex too early (e.g., from query string on first render), MudBlazor tries to access a panel index that doesn’t exist yet → ArgumentOutOfRangeException.

Working Solution: Render <MudTabs> only after the first render.

Razor

@if (_renderTabs)
{
    <MudTabs @ref="tabs"
             ActivePanelIndex="@_activePanelIndex"
             ActivePanelIndexChanged="OnActivePanelChanged">
        <MudTabPanel Text="Details" />
        <MudTabPanel Text="Tab 2" Disabled="@(!_enableTab2)" />
        <MudTabPanel Text="Tab 3" Disabled="@(!_enableTab3)" />
    </MudTabs>
}

Code-behind

private bool _renderTabs;
private int _activePanelIndex = 0;
private int? _pendingIndexFromQuery;

protected override async Task OnParametersSetAsync()
{
    if (TabFromQuery is int i && i >= 0)
        _pendingIndexFromQuery = i;
}

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    {
        _renderTabs = true;
        await InvokeAsync(StateHasChanged);
        return;
    }

    if (tabs is not null && _pendingIndexFromQuery.HasValue)
    {
        var index = Math.Min(_pendingIndexFromQuery.Value, tabs.Panels.Count - 1);
        _activePanelIndex = index;
        tabs.ActivatePanel(index);
        _pendingIndexFromQuery = null;
        StateHasChanged();
    }
}

Why this works

  • On the 1st render, MudTabs is not rendered yet → avoids the internal out-of-range access.

  • On the 2nd render, all tab panels are registered, so activating a tab by index is safe.


Sign up to request clarification or add additional context in comments.

2 Comments

FYI: Stack Overflow has a policy that bans AI generated content. Using AI to answer your question is OK, copy pasting the answer verbatim isn't.
As your entire answer is in a quote block, who/what are you quoting here? When quoting a different source you must including a link to the original source and (ideally) cite the author (if possible). Your post should also not be entirely made up of copied content; it must contain original material and should not be mostly quoted content. See How to reference material written by others. (This comment is not addressing the Gen AI claim in a prior comment.)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.