Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
stefanloerwald opened this issue Aug 7, 2020 · 2 comments
Labels
area-blazor Includes: Blazor, Razor Components ✔️ Resolution: Duplicate Resolved as a duplicate of another issue Status: Resolved

Comments

@stefanloerwald
Copy link

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.

@stefanloerwald
Copy link
Author

Side note: the no_render static object is only necessary because currently there will be a NRE when the first argument to Factory.Create<T> is null. null is probably a more idiomatic way of saying "please no implicit rendering!".

@javiercn javiercn added the area-blazor Includes: Blazor, Razor Components label Aug 7, 2020
@pranavkm
Copy link
Contributor

Thanks @stefanloerwald. We're tracking this feature as part of #18919. Closing this as a dup.

@pranavkm pranavkm added the ✔️ Resolution: Duplicate Resolved as a duplicate of another issue label Aug 10, 2020
@ghost ghost added the Status: Resolved label Aug 10, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Sep 9, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-blazor Includes: Blazor, Razor Components ✔️ Resolution: Duplicate Resolved as a duplicate of another issue Status: Resolved
Projects
None yet
Development

No branches or pull requests

3 participants