Skip to content

Suggestion for a @renderkey in Blazor's Razor syntax #64764

@lskyum

Description

@lskyum

Is there an existing issue for this?

  • I have searched the existing issues

Is your feature request related to a problem? Please describe the problem.

UI's with many components like lists, trees and tables can perform really bad, because too many components gets rendered at every update. The Blazor change detecting easily gets thrown off because of event handlers like:

@foreach (var item in items)
{
    <Item OnClick="@(() => HandleClick(item))">Click me</Item>
}

Because the event handler is a new delegate object each render the whole Item component also gets rendered.
One fix is to implement ShouldRender in the Item component, but that gets really tedious.

I have made my own RenderBlocker component that simply takes a Key and uses it to evaluate if a new render is needed. Internally it uses ShouldRender and compares the Key to previous render.

Used like this:

@foreach (var item in items)
{
    <RenderBlocker Key="@item">
        <Item OnClick="@(() => HandleClick(item))">Click me</Item>
    </RenderBlocker>
}

Describe the solution you'd like

The suggestion is to add a @renderkey to the razor syntax that behaves exactly like my custom RenderBlocker component.

@foreach (var item in items)
{
    <Item @renderkey="@item" OnClick="@(() => HandleClick(item))">Click me</Item>
}

Maybe extended with custom comparer:

@foreach (var item in items)
{
    <Item @renderkey="@item" @renderkey:comparer="@MyComparer" OnClick="@(() => HandleClick(item))">Click me</Item>
}

Because item is the same as last render, the whole component is re-used. (And also sub components)

Additional context

Note that the existing @key doesn't solve this issue, but my suggestion with @renderkey follows a similar syntax.

The code for RenderBlocker in case anyone needs it:

@typeparam TKey

@ChildContent

@code {
	[Parameter]
	public RenderFragment? ChildContent { get; set; }

	[Parameter]
	public TKey? Key { get; set; }

	[Parameter]
	public IEqualityComparer<TKey>? Comparer { get; set; }

	private TKey? previousKey;
	private bool firstFlag = true;

	protected override void OnAfterRender(bool firstRender)
	{
		firstFlag = false;
		previousKey = Key;
	}

	protected override bool ShouldRender()
	{
		if (firstFlag) return true;
		if ((Comparer ?? EqualityComparer<TKey>.Default).Equals(Key, previousKey)) 
		{
			return false;
		}
		return true;
	}
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-blazorIncludes: Blazor, Razor Components

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions