Skip to content

Re-render on EventCallback<T> usage cannot be avoided in Blazor: make it possible and at the same time remove reference to this #24655

@stefanloerwald

Description

@stefanloerwald

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

Considering this component:

public partial class Component : ComponentBase
{
    [Parameter] public EventCallback<int> ValueChanged { get; set; }
}

Sometimes it's useful to store RenderFragments in fields, e.g. for templates:

@code {
    private RenderFragment template = @<Component />;
}

As soon as the ValueChanged parameter is used, this breaks:

@code {
    private RenderFragment template = @<Component ValueChanged="(v) => {}"/>;
}

This doesn't compile, as the usage of ValueChanged will insert this into the generated code, which isn't available in a static initializer. The crux here: I don't even want the implicit render! So while the initialization can be moved to the constructor, this isn't really an improvement, because there will now the unwanted render.

Even in regular markup for a component, there is currently no way to use this component without having an implicit render after the invocation of the callback:

<Component ValueChanged="(v) => { ThisWillBeFollowedByARender(); }" />

as it translates to (code cleaned up for clarity)

protected override void BuildRenderTree(RenderTreeBuilder __builder)
{
    __builder.OpenComponent<Component>(0);
    __builder.AddAttribute(1, "ValueChanged", RuntimeHelpers.TypeCheck<EventCallback<System.Int32>>(
        EventCallback.Factory.Create<System.Int32>(this, (v) => { ThisWillBeFollowedByARender(); })));
    __builder.CloseComponent();
}

It would be good to have a way to generate an EvenCallback<T> without the implicit re-render. This is already feasible with:

// private static readonly object no_render = new object();
builder.AddAttribute(seq, "ValueChanged", EventCallback.Factory.Create<System.Int32>(no_render, (v) => { ThisWillBeFollowedByARender(); });

So far so good for manual generation of BuildRenderTree. Sadly there's no way using razor syntax to achieve the same:

<Component ValueChanged="EventCallback.Factory.Create<int>(prevent_render, (v) => { })" />

translates to

 __builder.AddAttribute(1, "ValueChanged", RuntimeHelpers.TypeCheck<EventCallback<System.Int32>>(
    EventCallback.Factory.Create<System.Int32>(this, 
             EventCallback.Factory.Create<int>(prevent_render, (v) => { })
)));

So the razor compiler wraps the EventCallback.Factory.Create<int>(prevent_render, (v) => {}) in a EventCallback.Factory.Create<System.Int32>(this, ___). This re-introduces the this.

Describe the solution you'd like

I would like to have a way to get rid of the callback and with that also the reference to this. This could be done e.g. with

<Component ValueChanged:norender="(v) => {}"/> <!--I'm fine with other syntax too. Just a suggestion-->

translating to

 __builder.AddAttribute(1, "ValueChanged", RuntimeHelpers.TypeCheck<EventCallback<System.Int32>>(
    EventCallback.Factory.CreateWithoutRender<System.Int32>((v) => { })
)));

And Factory.CreateWithoutRender<T> can be implemented as (sketch):

private static readonly object no_render = new object();
EventCallback<T> CreateWithoutRender<T>(Action<T> callback) => Create<T>(no_render, callback);

I would actually already be happy with the razor compiler not wrapping my event callback with another EventCallback.Factory.Create, but I think preventing implicit rendering would be useful to many users and the implementation of this can enable the other use case too.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions