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

Blazor InputSelect does not select the value when using SSR #53167

Closed sikora507 closed 4 months ago

sikora507 commented 9 months ago

Is there an existing issue for this?

Describe the bug

I've been following input components documentation: https://learn.microsoft.com/en-us/aspnet/core/blazor/forms/input-components?view=aspnetcore-8.0

And I've created the application with starship form page: image

After I provide all values and submit the form, the "Primary Classification" InputSelect component clears it's value, and when I try to click the Submit button again, I get the validation error. But the rest of the values are preserved. Only the one from InputSelect is cleared out.

The InputSelect component looks like this, straight from the docs: image

The Submit method logs the values properly and I can see the Classification that i've selected. image

But after the first post, the selected value is cleared.

I've provided the demo app on github: https://github.com/sikora507/blazor-selectinput-issue

The issue does not occur when I use @rendermode InteractiveServer

Expected Behavior

After submitting the form, the selection should remain the same like for the rest of input controls

Steps To Reproduce

  1. run git clone git@github.com:sikora507/blazor-selectinput-issue.git
  2. run the bazor app
  3. Fill the form in Forms page: image
  4. Click submit. The selection will be cleared image
  5. Trying submitting the form again with cleared form celection will cause validation error: image

Exceptions (if any)

No response

.NET Version

8.0.100

Anything else?

IDE: JetBrains Rider 2023.3.1

javiercn commented 9 months ago

The value on the select element is only in the browser. When the form the page is reloaded and any state from the client is overriden with state from the server, which doesn't mark any of the options as checked.

To Address this you need to mark the option as checked explicitly when rendering from the server:

<option value="">Select classification ...</option>
<option checked="@(Model!.Classification == "Exploration")" value="Exploration">Exploration</option>
<option checked="@(Model!.Classification == "Diplomacy")" value="Diplomacy">Diplomacy</option>
<option checked="@(Model!.Classification == "Defense")" value="Defense">Defense</option>

If we wanted to improve this, we will need to do something like InputRadio

ghost commented 9 months ago

Thanks for contacting us.

We're moving this issue to the .NET 9 Planning milestone for future evaluation / consideration. We would like to keep this around to collect more feedback, which can help us with prioritizing this work. We will re-evaluate this issue, during our next planning meeting(s). If we later determine, that the issue has no community involvement, or it's very rare and low-impact issue, we will close it - so that the team can focus on more important and high impact issues. To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.

guardrex commented 9 months ago

I'm working on covering this, but there's a strange behavior ...

In the form's original state in the sample app, it's true that the option value isn't retained on form submission.

As you point out @sikora507, if the Interactive Server render mode is applied to the component ...

@rendermode InteractiveServer

... the select retains the correct option state across form submissions.

However, the sample app has the Interactive Server render mode inherited via the Routes component ...

<Routes @rendermode="InteractiveServer" />

There's clearly a difference for the browser's retention of the selected option.

I currently have the WIP lingo as ...

When the model property for the ship's classification (Classification) is set, the option matching the model is checked. For example, checked="@(Model!.Classification == "Exploration")" for the classification of an exploration ship. The reason for explicitly setting the checked option is that the value of a <select> element is only present in the browser. If the form is rendered on the server after it's submitted, any state from the client is overridden with state from the server, which doesn't ordinarily mark an option as checked. By setting the checked option from the model property, the classification always reflects the model's state. This preserves the classification selection across form submissions that result in the form rerendering on the server. In situations where the form isn't rerendered on the server, such as when the Interactive Server render mode is applied directly to the component, explicit assignment of the checked option isn't necessary.

javiercn commented 9 months ago

I'm working on covering this, but there's a strange behavior ...

In the form's original state in the sample app, it's true that the option value isn't retained on form submission.

As you point out @sikora507, if the Interactive Server render mode is applied to the component ...

@rendermode InteractiveServer

... the select retains the correct option state across form submissions.

However, the sample app has the Interactive Server render mode inherited via the Routes component ...

<Routes @rendermode="InteractiveServer" />

There's clearly a difference for the browser's retention of the selected option.

I currently have the WIP lingo as ...

When the model property for the ship's classification (Classification) is set, the option matching the model is checked. For example, checked="@(Model!.Classification == "Exploration")" for the classification of an exploration ship. The reason for explicitly setting the checked option is that the value of a <select> element is only present in the browser. If the form is rendered on the server after it's submitted, any state from the client is overridden with state from the server, which doesn't ordinarily mark an option as checked. By setting the checked option from the model property, the classification always reflects the model's state. This preserves the classification selection across form submissions that result in the form rerendering on the server. In situations where the form isn't rerendered on the server, such as when the Interactive Server render mode is applied directly to the component, explicit assignment of the checked option isn't necessary.

The repro app doesn't have interactive server on it, as far as I can see

https://github.com/sikora507/blazor-selectinput-issue/blob/main/BlazorForms/Components/App.razor#L16

When you use @InteractiveServer it preserves the state because the code is running the component in stateful mode, the issue here appears when the component is stateless (a.k.a SSR).

guardrex commented 9 months ago

The strange behavior 👽 that I'm referring to is WRT the Blazor sample app, not the repro app.

I was inheriting the interactive server render mode from the Routes component for all of the app's form example components.

With the interactive server render mode assigned to the Routes component, the select in that example forms component was losing its options setting on form submission using the old code that I had ... the code that @sikora507 shows in the OP. Now that I've updated the Blazor sample to your new approach that explicitly sets option from the model, it's all good now no matter if the interactive render mode is assigned or not or if it's assigned to the Routes component. If that sounds like it shouldn't be happening (i.e., setting from the Routes component with the original code not "remembering" the option), then I'll double-check and report back, possibly with a repro project.

AFAICT, the guidance paragraph above :point_up: is correct. However, I didn't get a reason in place, so I'll probably make one more change (to that last line of the paragraph) to incorporate this bit that you provided ...

When you use @InteractiveServer it preserves the state because the code is running the component in stateful mode

UPDATE: Done! I made an update to that line ...

In situations where the form isn't rerendered on the server, such as when the Interactive Server render mode is applied directly to the component, explicit assignment of the checked option from the model isn't necessary because Blazor preserves the state for the <select> element on the client.

I can change it if that's still not quite capturing the right sentiment.

Note that I'm still saying "directly to the component" because I didn't see preservation of the checked option via assigning the render mode to the Routes component globally.

janusqa commented 8 months ago

I think I have the same issue but the suggestion is not working for me.

                        <InputSelect @bind-Value="ProductDto.CategoryId" class="form-select">
                            <option value="0" disabled selected>-- Select Category --</option>
                            @foreach (var category in Categories)
                            {
                                <option checked="@(ProductDto.CategoryId == category.Id ? true: false)"
                                    value="@category.Id">@category.Name
                                </option>
                            }
                        </InputSelect>
krompaco commented 5 months ago

For OPTION-elements the attribute to use is SELECTED not CHECKED.

https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option#selected

mkArtakMSFT commented 4 months ago

@MackinnonBuck is anything pending here? Should this be closed?

MackinnonBuck commented 4 months ago

Yes, let's close this out for now. This issue can track the docs change, and if we get reports that the workaround is not sufficient, then we could use a new issue to track a framework change.