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

Render Mode Auto Interactive does not wait for next access of a component before swapping to WASM #52899

Closed CaveRock closed 9 months ago

CaveRock commented 10 months ago

Is there an existing issue for this?

Describe the bug

RenderMode Auto does not wait to replace Server Interactive mode with WasmInteractive until next use of component, it does it as soon as wasm has downloaded. Any user interaction with the component, like entering text into an input element, is wiped when the component is replaced while user is still interacting with the component.

Expected Behavior

The expected behavior is that the component will load initially as server interactive and then stay as server interactive until the next time the component is used.

Steps To Reproduce

Set component to use RenderMode Auto, for example an Input Form. Set browser dev tools to replicate 3g connection in order to see the issue more easily. Run Application. Click Form Problem Nav Link. Cntrl F5 to hard refresh to ensure Server Interactive mode initially. On first load of the input form component it will load as server interactive as expected, however, as soon as WASM has downloaded the component is swapped out immediately. User can type into the Input Form while it is Server Interactive, but the component will be completely replaced as soon as WASM version of component is available, this wipes all user input from the form.

Attached video of the issue demonstrated with a Form

https://github.com/dotnet/aspnetcore/assets/4638922/e600b181-2174-463a-9aec-32e4f8484722

Repo to reproduce

Additional example with different behavior can be seen with Counter component in the linked repo.

Cntrl F5 refresh while on the Counter component, attempt to interact with Click Me button. There will be no increment until the Server Interactive version of the component has been replaced with the wasm component, this replacement will happen without having to navigate away from the component.

Exceptions (if any)

No response

.NET Version

8

Anything else?

No response

javiercn commented 10 months ago

@CaveRock thanks for contacting us.

We'll take a look and update this issue.

@mkArtak parking this in 8.x as if it repro's it's a patch candidate

kevon-vems commented 9 months ago

I am also experiencing this.

MackinnonBuck commented 9 months ago

Hi, thanks for reaching out!

There seems to be a small misconception here. In the FormProblem component in the repro you provided, there's the following code:

<h3>FormProblem @(OperatingSystem.IsBrowser() ? "WASM" : "SERVER")</h3>

This line implies that there are two possible states for the component to be in, but there are actually three:

  1. Prerendered (not yet interactive)
  2. Server interactive
  3. WebAssembly interactive

The code in FormProblem conflates the first two cases. You'll notice that even if you change the render mode of the page to InteractiveWebAssembly, the bug seems to appear. That's because the transition you're experiencing is from "prerendered" to "WebAssembly interactive", not from "Server interactive" to "WebAssembly interactive". The Auto render mode never changes the interactive render mode of an existing component.

The problem you're likely experiencing is https://github.com/dotnet/aspnetcore/issues/42561, which tracks improving the transition from "prerendered" to "interactive".

If you want a component that correctly indicates what the current render mode is, something like this should work:

<p>Render mode: @renderMode</p>

@code {
    private string renderMode = "SSR (not interactive)";

    protected override void OnAfterRender(bool firstRender)
    {
        if (firstRender)
        {
            renderMode = OperatingSystem.IsBrowser() ? "WebAssembly interactive" : "Server interactive";
            StateHasChanged();
        }
    }
}

Hope this helps! Closing out this issue as a dupe of #42561.

CaveRock commented 9 months ago

@MackinnonBuck thanks for the feedback.

I think there is still an issue here, did you watch the video I attached? The issue is that the server rendered component is being swapped out with a wasm version whilst the user is still interacting with the component which is wiping the state. I only placed:

<h3>FormProblem @(OperatingSystem.IsBrowser() ? "WASM" : "SERVER")</h3>

so that one can see this swap occurring along with the entire form state being wiped. I do understand the pre-render is a '3rd' state, the pre-render is not the issue, the swap from server to wasm is the issue.

My issue is really apparent when using a slow connection, which is common where I am, and any interaction by the user with the form will be wiped as soon as the wasm binaries download completes, whereas the wasm version should only be used the next time the user navigates to the form component. In order to really see this one needs to use browser dev tools to throttle connection to "slow 3g'.

I may still be miss-understanding and apologise if that is the case, but would appreciate if we could just make sure we are both viewing this from the same 'lense'.

Thanks

MackinnonBuck commented 9 months ago

Hi @CaveRock,

I understand what you're saying and have seen your video (and tested your repro to make sure I'm not missing anything else), and I believe what I said in my previous comment is accurate. I appreciate there is a problem here that can create the bad UX you're seeing, so I'll do my best to clarify what's happening.

The issue is that the server rendered component is being swapped out with a wasm version whilst the user is still interacting with the component which is wiping the state... the pre-render is not the issue, the swap from server to wasm is the issue.

It's not swapping from server to wasm interactivity. Even though it might appear this way, the line of code in your example does not indicate the interactive render mode. If you remove the @rendermode from the page, "SERVER" will still get rendered even though the page is not interactive. Therefore, the component displaying "SERVER" is not an indication that the component is using server interactivity. It actually means that either:

I believe your repro demonstrates the former. Note that interactivity isn't required for the user to start entering text into the text input - the user can do that on a non-interactive page, as they could on a plain HTML input element.

I'm not trying to suggest that there isn't a problem here (as many developers have indicated being affected by this in #42561). I'm trying to say that the problem might not be what's reported in this issue.


To demonstrate what I'm saying, could you please try the following? Change the render mode on the FormProblem component to InteractiveWebAssembly. I think you'll find that the problem still appears. You could also check the network tab in the browser dev tools window and observe that despite this transition, no Blazor Server websocket connection was opened. See also the code I pasted in my last comment, since that will help correctly distinguish between the 3 render modes.

One potential workaround is to initially render the page with the input elements disabled, then enable them after the component becomes interactive. This will prevent the user from entering text that gets wiped after the transition to interactivity.

Hope this helps.

CaveRock commented 9 months ago

@MackinnonBuck thanks for the detailed response.

Something that could help me understand what is considered statically rendered vs interactive, if the initial form that loads is able to produce form validation messages based on the attributes placed on the form model properties when hitting the submit button with invalid inputs, would that be considered that the render is achieved "interactivity"?

I ask as I have another repo that I can show the initial load, I can interact with the form, hit the submit button and get all the expected validation errors, then a couple seconds later (when I assume the wasm download completes) the form is wiped, all inputs reset and all validation state reset. This to me feels like interactive and not the initial static render.

If you agree then I will put together a new repo that demonstrates this behaviour.

MackinnonBuck commented 9 months ago

@CaveRock in the case you just described, the validation messages are being displayed because they were included in the server's response HTML after the form post. If you were to make the page non-interactive, the validation messages should still show up in the same way.

An interactive form would show validation messages immediately after an input element loses focus, even before the form gets submitted.

Interactive functionality is that which can apply updates to the existing page without requesting new page content from the server. i.e., if a component's logic runs using WebAssembly, or if requires a Blazor Server circuit, it's considered to be interactive.