RadzenTabs: ensure render state is restored when tab event handlers throw

Follow-up to the #2558 fix. In SelectTab the StateHasChanged call was skipped
if Change/SelectedIndexChanged threw, leaving the component stale (multiple
tabs visually active); in SelectTabOnClient a throw left shouldRender stuck at
false, blocking all further renders. Wrap both in finally so the render state
is always restored. Addresses review feedback on commit df1dfa713.

Fixes #2558
This commit is contained in:
Vladimir Enchev
2026-05-29 15:19:30 +03:00
parent 38ea391bfc
commit 61b2982f0d
2 changed files with 47 additions and 15 deletions

View File

@@ -230,6 +230,27 @@ namespace Radzen.Blazor.Tests
Assert.True(component.RenderCount > renderCountBefore);
}
[Fact]
public void Tabs_ChangeHandlerThrows_StillRendersSelectedTab()
{
using var ctx = new TestContext();
var component = ctx.RenderComponent<RadzenTabs>(parameters => parameters
.Add(p => p.SelectedIndex, 0)
.Add(p => p.Change, (int _) => throw new System.InvalidOperationException("boom"))
.Add(p => p.Tabs, TabsFragmentWithContent(("First", "First-Content"), ("Second", "Second-Content")))
);
Assert.Contains("First-Content", component.Markup);
var secondTab = component.FindAll("button[role='tab']")[1];
var ex = Record.Exception(() => secondTab.Click());
Assert.NotNull(ex);
Assert.Contains("Second-Content", component.Markup);
Assert.DoesNotContain("First-Content", component.Markup);
}
[Fact]
public void Tabs_NonNavigationKeyDown_DoesNotBlockSubsequentRenders()
{

View File

@@ -210,22 +210,27 @@ namespace Radzen.Blazor
SetFocusedIndex();
if (raiseChange)
try
{
await Change.InvokeAsync(selectedIndex);
await SelectedIndexChanged.InvokeAsync(selectedIndex);
try
{
await Element.FocusAsync(preventScroll: true);
}
catch (JSDisconnectedException)
if (raiseChange)
{
await Change.InvokeAsync(selectedIndex);
await SelectedIndexChanged.InvokeAsync(selectedIndex);
try
{
await Element.FocusAsync(preventScroll: true);
}
catch (JSDisconnectedException)
{
}
}
}
StateHasChanged();
finally
{
StateHasChanged();
}
}
/// <inheritdoc />
@@ -342,9 +347,15 @@ namespace Radzen.Blazor
}
shouldRender = false;
await Change.InvokeAsync(selectedIndex);
await SelectedIndexChanged.InvokeAsync(selectedIndex);
shouldRender = true;
try
{
await Change.InvokeAsync(selectedIndex);
await SelectedIndexChanged.InvokeAsync(selectedIndex);
}
finally
{
shouldRender = true;
}
try
{