dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.37k stars 9.99k forks source link

CascadingParameter in EditForm causes InputText to re-render on every keystroke which loses cursor position. #54208

Closed TrieBr closed 7 months ago

TrieBr commented 7 months ago

Is there an existing issue for this?

Describe the bug

I'm trying to extend Microsoft.AspNetCore.Components.Forms.EditForm and Microsoft.AspNetCore.Components.Forms.InputText, but I found a weird combination that causes the input to re-render on every keystroke which moves the cursor to the end.

public class EditForm : Microsoft.AspNetCore.Components.Forms.EditForm
{

    public ThisCanBeAnyClass ThisCanBeAnyClass { get; set; } = new();

    protected override void BuildRenderTree(RenderTreeBuilder builder)
    {
        Debug.Assert(EditContext != null);
        builder.OpenRegion(EditContext.GetHashCode());
        builder.OpenComponent<CascadingValue<ThisCanBeAnyClass>>(1);
        builder.AddComponentParameter(0, "IsFixed", false);
        builder.AddComponentParameter(1, "Name", "ThisCanBeAnyClass");
        builder.AddComponentParameter(2, "Value", ThisCanBeAnyClass);
        builder.AddComponentParameter(
            3,
            "ChildContent",
            (RenderFragment)((builder2) => { base.BuildRenderTree(builder2); }
            ));
        builder.CloseComponent();
        builder.CloseRegion();
    }

}

I have another class that inherits Microsoft.AspNetCore.Components.Forms.InputText:

public class InputTextExtend : Microsoft.AspNetCore.Components.Forms.InputText
{
    [CascadingParameter(Name = "ThisCanBeAnyClass")]
    public ThisCanBeAnyClass? ThisCanBeAnyClass { get; set; }

     protected override void BuildRenderTree(RenderTreeBuilder builder)
    {
        builder.OpenElement(0, "input");
        builder.AddMultipleAttributes(1, AdditionalAttributes);
        if (!string.IsNullOrEmpty(NameAttributeValue))
            builder.AddAttribute(2, "name", NameAttributeValue);
        builder.AddAttribute(3, "class", "form-control");
        builder.AddAttribute(4, "value", CurrentValueAsString);
        builder.AddAttribute(5, "oninput", EventCallback.Factory.CreateBinder<string?>(this, __value => CurrentValueAsString = __value, CurrentValueAsString));
        builder.SetUpdatesAttributeName("value");
        builder.AddElementReferenceCapture(6, __inputReference => Element = __inputReference);
        builder.CloseElement();
    }
}

In this scenario, keystokes ("oninput" event) cause the cursor to always move to the end of the input due to re-rendering. The following change seem to fix the issue.

  1. Remove CascadingValue from EditForm
  2. Change the CascadingParameter type or name in the InputText
  3. In EditForm set IsFixed to true.
  4. The following does work, which should be equivalent to what I'm trying to do, but my code should work as far as I know.
    <Microsoft.AspNetCore.Components.Forms.EditForm Model="Model">
    <CascadingValue Value="@ThisCanBeAnyClass">
    <InputText @bind-Value="Model.SomeText" />
    <InputTextExtend @bind-Value="Model.SomeText2" />
    </CascadingValue>
    </Microsoft.AspNetCore.Components.Forms.EditForm>

I'm confused why this specific combination causes this bug.

Expected Behavior

The component should not continually re-render with each keystoke and move the cursor to the end.

Steps To Reproduce

I made a minimal repro here: https://github.com/TrieBr/blazor-input-bug/tree/main

Here is the hosted WASM page: https://triebr.github.io/blazor-input-bug (Go to "Counter" page)

In the first textbox, click in the middle and type a character. The cursor moves to the end. The problem doesn't occur in the second one which should be equivalent based on the code.

Exceptions (if any)

None

.NET Version

8.0.100

Anything else?

.NET SDK: Version: 8.0.100 Commit: 57efcf1350 Workload version: 8.0.100-manifests.6c33ef20

Runtime Environment: OS Name: Mac OS X OS Version: 14.1 OS Platform: Darwin RID: osx-arm64 Base Path: /usr/local/share/dotnet/sdk/8.0.100/

.NET workloads installed: Workload version: 8.0.100-manifests.6c33ef20 There are no installed workloads to display.

Host: Version: 8.0.0 Architecture: arm64 Commit: 5535e31a71

.NET SDKs installed: 6.0.201 [/usr/local/share/dotnet/sdk] 6.0.402 [/usr/local/share/dotnet/sdk] 6.0.404 [/usr/local/share/dotnet/sdk] 7.0.100 [/usr/local/share/dotnet/sdk] 7.0.101 [/usr/local/share/dotnet/sdk] 7.0.302 [/usr/local/share/dotnet/sdk] 8.0.100-rc.1.23463.5 [/usr/local/share/dotnet/sdk] 8.0.100-rc.2.23502.2 [/usr/local/share/dotnet/sdk] 8.0.100 [/usr/local/share/dotnet/sdk]

.NET runtimes installed: Microsoft.AspNetCore.App 6.0.3 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 6.0.10 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 6.0.12 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 7.0.0 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 7.0.1 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 7.0.5 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 8.0.0-rc.1.23421.29 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 8.0.0-rc.2.23480.2 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 8.0.0 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App] Microsoft.NETCore.App 6.0.3 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App] Microsoft.NETCore.App 6.0.10 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App] Microsoft.NETCore.App 6.0.12 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App] Microsoft.NETCore.App 7.0.0 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App] Microsoft.NETCore.App 7.0.1 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App] Microsoft.NETCore.App 7.0.5 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App] Microsoft.NETCore.App 8.0.0-rc.1.23419.4 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App] Microsoft.NETCore.App 8.0.0-rc.2.23479.6 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App] Microsoft.NETCore.App 8.0.0 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]

Other architectures found: x64 [/usr/local/share/dotnet/x64]

Environment variables: Not set

global.json file: Not found

Learn more: https://aka.ms/dotnet/info

Download .NET: https://aka.ms/dotnet/download

TrieBr commented 7 months ago

NOTE: I was able to workaround this by storing my state in EditContext.Properties instead of my own CascadingValue.

javiercn commented 7 months ago

@TrieBr thanks for contacting us.

Very likely it has to do with https://github.com/TrieBr/blazor-input-bug/blob/main/BlazorInputBug/InputBug/Pages/EditForm.cs#L26-L27 and it's by design.

https://github.com/TrieBr/blazor-input-bug/blob/main/BlazorInputBug/InputBug/Pages/InputTextExtend.cs#L23

Your component receives a non-fixed cascading parameter that is not a well-known immutable type (for Blazor) so that it's going to cause it to re-render.

There's likely a subtle difference between the generated code and the code you wrote by hand.

Use EmitCompilerGeneratedFiles on your csproj in order to inspect the output from the Razor Source generator and that should help you spot any difference.

dotnet-policy-service[bot] commented 7 months ago

Hi @TrieBr. We have added the "Needs: Author Feedback" label to this issue, which indicates that we have an open question for you before we can take further action. This issue will be closed automatically in 7 days if we do not hear back from you by then - please feel free to re-open it if you come back to this issue after that time.

dotnet-policy-service[bot] commented 7 months ago

This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for 4 days. It will be closed if no further activity occurs within 3 days of this comment. If it is closed, feel free to comment when you are able to provide the additional information and we will re-investigate.

See our Issue Management Policies for more information.