@Key instruction doesn't work when call Render Tree Builder by hands in Blazor WebAssembly (.NET 5.0).
See original GitHub issueDescribe the bug
Hi, friend. It’s an issue about @Key instruction of ASP.NET Core Blazor in Web Assembly.
Requirement:
I am going to display many sub-components in a container-component, but the types of sub-components are different and cannot be determined during coding.
Solution:
So I have to realize this function through calling RenderTreeBuilder and RenderFragment by hands.
Issue:
But I found an issue that @Key instruction doesn’t work in my solution, for example: I have 5 sub-components with @key from 1 to 5 in my container-component now, the no.4 and no.5 sub-components are re-rendered after I removed the no.3 sub-component.
All sub-components behind the one I just removed will be re-rendered.
To Reproduce
You can use my simple project to reproduce this issue, Index.razor will show you that.
1. Create a ASP.NET Core Blazor in Web Assembly of .NET 5.0
2. Create Sub-Component
ItemComponent.razor
@inject ILogger<ItemComponent> logger;
<button class="btn btn-danger m-2">@Value</button>
@code {
[Parameter]
public int Value { get; set; }
private void ClickMe()
{
this.logger.LogInformation($"Click {this.Value}");
}
protected override void OnAfterRender(bool firstRender)
{
base.OnAfterRender(firstRender);
this.logger.LogWarning($"After render {this.Value}");
}
}
3. Create a Container-Component which @Key instruction is working well.
@Key instruction is working well in this container component, But it’s not my solution cause it can not meet my requirement.
ContainerComponent.razor
@inject ILogger<ContainerComponent> logger;
<div class="mx-2 my-4 p-3 rounded rounded-lg shadow border border-danger">
<h3>ContainerComponent</h3>
<button class="btn btn-block btn-primary" @onclick="ClickAddButton">Add Ten</button>
<button class="btn btn-block btn-warning" @onclick="ClickDeleteButton">Delete One</button>
<hr />
@foreach (var item in this.Items)
{
var key = $"Item_{item}";
<ItemComponent @key="key" Value="item"></ItemComponent>
}
</div>
@code {
private List<int> Items = new List<int>();
private int start = 0;
protected override void OnInitialized()
{
base.OnInitialized();
ClickAddButton();
}
private void ClickAddButton()
{
const int length = 10;
this.logger.LogInformation($"Add {length} more items");
Items.AddRange(Enumerable.Range(start, length));
start += length;
this.StateHasChanged();
}
private void ClickDeleteButton()
{
if (!Items.Any()) return;
int index = Items.Count() / 2;
this.logger.LogInformation($"Delete number {Items.ElementAt(index)} item");
Items.RemoveAt(index);
this.StateHasChanged();
}
}
4. Create my Container-Component which @Key instruction doesn’t work.
This is my solution, but @Key instruction is not working in this container-component.
RenderContainerComponent.razor
@inject ILogger<RenderContainerComponent> logger;
<div class="mx-2 my-4 p-3 rounded rounded-lg shadow border border-danger">
<h3>RenderContainerComponent</h3>
<button class="btn btn-block btn-primary" @onclick="ClickAddButton">Add Ten</button>
<button class="btn btn-block btn-warning" @onclick="ClickDeleteButton">Delete One</button>
<hr />
@foreach (var renderFragment in this.RenderFragments)
{
@renderFragment
}
</div>
@code {
private List<RenderFragment> RenderFragments = new List<RenderFragment>();
private Type ItemComponentType = typeof(ItemComponent);
private string PropertyOfItemComponent = nameof(ItemComponent.Value);
private int start = 0;
protected override void OnInitialized()
{
base.OnInitialized();
ClickAddButton();
}
private void ClickAddButton()
{
const int length = 10;
this.logger.LogInformation($"Add {length} more Render Fragments");
RenderFragments.AddRange(Enumerable.Range(start, length).Select(index =>
{
RenderFragment render = builder =>
{
var key = $"RenderFragment_{index}";
builder.OpenRegion(index);
// Have to render a component via generic function.
builder.OpenComponent(0, this.ItemComponentType);
builder.AddAttribute(1, PropertyOfItemComponent, index);
builder.SetKey(key);
builder.CloseComponent();
builder.CloseRegion();
};
return render;
}));
start += length;
this.StateHasChanged();
}
private void ClickDeleteButton()
{
if (!RenderFragments.Any()) return;
int index = RenderFragments.Count() / 2;
this.logger.LogInformation($"Delete no.{index} Render Fragment");
RenderFragments.RemoveAt(index);
this.StateHasChanged();
}
}
5. Display both Container-Components above in Index.razor
Index.razor
@page "/"
<h1>Hello, world!</h1>
Welcome to your new app.
<ContainerComponent></ContainerComponent>
<RenderContainerComponent></RenderContainerComponent>
6. What To Do:
- Start project and go to home page.
- Open Console of Browser and watch debug log.
- Click “Add Ten” and “Delete One” button in home page.
- You will find out that some of Sub-Components in RenderContainerComponent is logging “After render {Value}” in Browser’s Console, because @Key instruction is not working and these Sub-Components are re-rendered.
Further technical details
- ASP.NET Core version: .NET 5.0.101
- IDE: Visual Studio 2019 (16.8.3)
Thanks for your time! Waiting for your reply.
Issue Analytics
- State:
- Created 3 years ago
- Comments:5 (3 by maintainers)

Top Related StackOverflow Question
Thanks for clarifying. The issue here is that you have the
@keyon the component inside yourRenderFragment, rather than on the fragment itself. When Blazor rendersRenderFragmentinstances, it wraps them inside an invisible “region” marker to ensure that the diffing system knows to treat each one as an independent group. Since there’s no key on that region, the diffing system doesn’t have the same opinion that you do about how they should be matched up across renders.You can fix this by putting the
@keyon something wrapped around your render fragment. For example, here’s one way to do it: https://gist.github.com/SteveSandersonMS/257fc94ff8919ee12886a468d271d9aeIf you really wanted to avoid the
<span>for some reason, you could define another component that simply emits the renderfragment, and put the key on that instead.As of .NET 6, there will also be the option of using
<DynamicComponent>for this scenario, which simplifies things further as you don’t have to write your own logic withRenderTreeBuilder.Hi @SteveSandersonMS : Thanks for your explaining, I have never thought that the solution would be so tiny. It really helps a lot, and great looking forward to the .Net 6!