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

First option in select dropdown appears selected when no option matches the bound value #6126

Closed
theodorejb opened this issue Mar 25, 2021 · 10 comments · Fixed by #6170
Closed

Comments

@theodorejb
Copy link
Contributor

Describe the bug
When a select dropdown value is bound to a component variable, and this variable doesn't match any of the options in the dropdown, Svelte shows the first option as selected rather than blank. This causes bugs/unexpected behavior when the form is submitted (the submitted value doesn't match what the user sees).

To Reproduce
https://svelte.dev/repl/a7fdeec83c504c00b9706edc679330c6?version=3.35.0

Expected behavior
No option should be selected when none of the options match the bound value.

Uncomment the two setTimeout calls in the above REPL to see the expected behavior.

Information about your Svelte project:

  • Browser: Chromium 89, Firefox 87
  • Operating system: Windows 10
  • Svelte version: 3.35.0

Severity
I'm currently using Angular and would like to use Svelte instead. For my app it is important that select dropdowns be blank when none of the options match the bound value.

@Prinzhorn
Copy link
Contributor

Prinzhorn commented Mar 26, 2021

No option should be selected when none of the options match the bound value.

For my app it is important that select dropdowns be blank when none of the options match the bound value.

Does this even work in plain HTML? That's why every <select> ever has an option with an empty value that says "Please select a value...". I don't think HTML has a concept of not having an option selected. How would that be rendered?

Example:

https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select

Selection_841

Edit: here's another point to think about: How would a user undo their selection (go back to the no-selected state) without having an explicit empty <option>? So even if a <select> would by default not show any of the options it would be painful ux if a user could never go back to the state if the selection is optional.

@theodorejb
Copy link
Contributor Author

@Prinzhorn Thank you for the reply and thoughtful questions.

For single select elements in plain HTML, I think you're right that there isn't a concept of not having an option selected. However, in plain HTML there also isn't a concept of binding a select value to an object, null, or other non-string value as can be done in Svelte.

With a plain HTML form where the page is reloaded on submission, the submitted value always matches that of the selected option. But this expectation is broken in Svelte if an option is shown as selected when it doesn't match the bound value.

How would a user undo their selection (go back to the no-selected state) without having an explicit empty <option>? So even if a <select> would by default not show any of the options it would be painful ux if a user could never go back to the state if the selection is optional.

For optional dropdowns, you are correct that an explicit empty option is needed. For such forms it works fine to add a <option value={null}></option> element and set the variable bound to the select value back to null after the form is submitted. However, this doesn't work for required dropdowns, where the required attribute should prevent the form from being submitted if the user hasn't selected an option. Because an option with a value of {null} ends up with the string "null" in its value property, the browser won't prevent users from submitting the form with that option selected.

It seems like the only way I can get close to the expected behavior for required dropdowns is to add <option value hidden></option> to every required select element, and then set the bound value to an empty string initially and after the form is submitted. I can't find a way to make it work with null as the bound value.

Adding an extra option element and binding to a blank string seems a bit like a hacky workaround, though. My expectation is that when using a two-way binding Svelte would automatically ensure that an option is only selected if it matches the bound select value.

@Prinzhorn
Copy link
Contributor

Prinzhorn commented Mar 26, 2021

I think I misunderstood the original issue and the discussion was missing the point. In HTML there is no selectedIndex = -1, you cannot have a "no-selection" state with plain HTML. But you can with JavaScript.

So to rephrase your feature request: you want selectedIndex = -1 if none of the option[value] matches the bind:value. I still think that's odd. Because the state is still inconsistent: your value contains something but the select is empty (user doesn't know your state is not empty though)

Here's what I would do, which matches how basically every select I've ever encountered worked:

https://svelte.dev/repl/1cdd3422d8ef4a6eb12ddf0b25be6e82?version=3.35.0

Edit: I personally would be very cautions with binding objects and stick to your id field. If through some functionality later you update one of the objects to a different one (with the same id) you end up in an inconsistent state. If you have an id I personally would always use it and have a selectedId. But that depends entirely on your use-case, I just play safe and I hate subtle bugs.

Edit2: Also regarding the required attribute: if you don't play by the rules of HTML (e.g. binding objects) than don't expect HTML features to work. If you go that route you need a custom validation logic.

@theodorejb
Copy link
Contributor Author

theodorejb commented Mar 30, 2021

@Prinzhorn Thank you for the additional perspective.

So to rephrase your feature request: you want selectedIndex = -1 if none of the option[value] matches the bind:value.

Correct. This is how it works in Angular and Vue.js.

I still think that's odd. Because the state is still inconsistent: your value contains something but the select is empty (user doesn't know your state is not empty though)

If I may push back on this, the state actually would be consistent: no option would be selected since none of them match the bind:value. Then if the select input is required, the browser will prevent the form from being submitted in this state with no valid option selected. Currently, the state is more inconsistent since an option appears selected even though its value is different from the bind:value.

Here's what I would do, which matches how basically every select I've ever encountered worked:
https://svelte.dev/repl/1cdd3422d8ef4a6eb12ddf0b25be6e82?version=3.35.0

While this approach (manually disabling the submit button when the bound value is null) works for extremely simple forms, it breaks down in more complex real-world use cases.

E.g. in my production app I have a form with six required select dropdowns, along with a few text inputs. The native required attribute is all I need for the text inputs, but having to add manual null checks for all the select elements adds significant boilerplate for something that I would expect to "just work" when the dropdowns have a required attribute.

Furthermore, even if I add all those null checks, the user experience is not as good as the native browser required check, since the grayed out submit button doesn't make it clear to the user which field has a problem.

If you have an id I personally would always use it and have a selectedId...Also regarding the required attribute: if you don't play by the rules of HTML (e.g. binding objects) than don't expect HTML features to work.

I have the same issue whether I bind to objects or just IDs. Svelte still shows the first option as selected if the input is bound to null or another value that doesn't match one of the options. This inconsistent state leads to unexpected results when a user submits the form, unless a bunch of extra boilerplate code is added to mimic the browser's built in required check.

I did some experimentation with the Svelte code and it looks like a fix for this issue could be as simple as adding select.selectedIndex = -1; on the last line of the select_option function in dom.ts (https://github.com/sveltejs/svelte/blob/v3.36.0/src/runtime/internal/dom.ts#L223).

@Prinzhorn
Copy link
Contributor

Prinzhorn commented Mar 30, 2021

If I may push back on this, the state actually would be consistent: no option would be selected since none of them match the bind:value

Maybe we're having different interpretations of the term "consistent". No option is rendered as selected to the user but your selected variable contains something. That's what I mean by inconsistent. Currently it renders the first <option>, you want selectedIndex = -1. In either case it's inconsistent with your application state. The user doesn't see what your variable holds. If you'd do a fetch with your state you send something to the server that the user doesn't see.

To me personally it's a bug if your value doesn't match any of the options. Like I said to me having a special <option> with an empty value is standard practive. I'm not involved with Svelte at all, but I'm definitely against the change you propose. However, how about exposing the selectedIndex property so you can implemented the behavior you want. This would probably make both of us happy, it's backwards compatible and exposing DOM properties is regular Svelte business.

Something like this

<select selectedIndex={questions.indexOf(selected)} on:change={e => selected = questions[e.target.selectedIndex]}>

Edit: on a second thought I'm not against it anymore. I personally always have my selected value in the list of options so your use-case does not apply to me. And having selectedIndex = -1 makes it slightly more obvious to me that I have a bug. And to you it's a feature, so 👍 . I haven't look at the Svelte source, if you give a PR a try I'll definitely take a look at it.

@Prinzhorn
Copy link
Contributor

You can also use a custom action specific for this task https://svelte.dev/repl/368ce1cecc1b4bb4beb6259c70e0444e?version=3.35.0

theodorejb added a commit to theodorejb/svelte that referenced this issue Apr 6, 2021
Previously, when the bound value of a select element was set to `null` or any other value not matching one of the options, the first option would appear selected.
So even if the select element was `required`, users could submit the form and end up sending a different value than what it appeared they were submitting.

With this change, if a select element is bound to a value that doesn't match one of its options, then none of the options will appear selected.
This helps catch bugs during development when the bound value inadvertently doesn't match an option.
It also makes it possible to bind a `required` select element to `null`, and cause the browser to require users to explicitly select an option before the form can be submitted.

Resolves sveltejs#6126.

In order for the test to pass, it was necessary to update the jsdom dependency to the latest 15.x release, since v15.2.0 fixed `selectEl.value` when no `<option>` is selected to return the empty string.
See https://github.com/jsdom/jsdom/blob/15.2.1/Changelog.md#1520 and jsdom/jsdom@699ed6b.
theodorejb added a commit to theodorejb/svelte that referenced this issue Apr 6, 2021
Previously, when the bound value of a select element was set to `null` or any other value not matching one of the options, the first option would appear selected.
So even if the select element was `required`, users could submit the form and end up sending a different value than what it appeared they were submitting.

With this change, if a select element is bound to a value that doesn't match one of its options, then none of the options will appear selected.
This helps catch bugs during development when the bound value inadvertently doesn't match an option.
It also makes it possible to bind a `required` select element to `null`, and cause the browser to require users to explicitly select an option before the form can be submitted.

Resolves sveltejs#6126.

In order for the test to pass, it was necessary to update the jsdom dependency to the latest 15.x release, since v15.2.0 fixed `selectEl.value` when no `<option>` is selected to return the empty string.
See https://github.com/jsdom/jsdom/blob/15.2.1/Changelog.md#1520 and jsdom/jsdom@699ed6b.
@theodorejb
Copy link
Contributor Author

@Prinzhorn I created PR #6170 to resolve this now. The fix only required adding a single line to Svelte's select_option helper function.

theodorejb added a commit to theodorejb/svelte that referenced this issue Apr 7, 2021
Previously, when the bound value of a select element was set to `null` or any other value not matching one of the options, the first option would appear selected.
So even if the select element was `required`, users could submit the form and end up sending a different value than what it appeared they were submitting.

With this change, if a select element is bound to a value that doesn't match one of its options, then none of the options will appear selected.
This helps catch bugs during development when the bound value inadvertently doesn't match an option.
It also makes it possible to bind a `required` select element to `null`, and cause the browser to require users to explicitly select an option before the form can be submitted.

Resolves sveltejs#6126.

In order for the test to pass, it was necessary to update the jsdom dependency to the latest 15.x release, since v15.2.0 fixed `selectEl.value` when no `<option>` is selected to return the empty string.
See https://github.com/jsdom/jsdom/blob/15.2.1/Changelog.md#1520 and jsdom/jsdom@699ed6b.
theodorejb added a commit to theodorejb/svelte that referenced this issue Apr 19, 2021
Previously, when the bound value of a select element was set to `null` or any other value not matching one of the options, the first option would appear selected.
So even if the select element was `required`, users could submit the form and end up sending a different value than what it appeared they were submitting.

With this change, if a select element is bound to a value that doesn't match one of its options, then none of the options will appear selected.
This helps catch bugs during development when the bound value inadvertently doesn't match an option.
It also makes it possible to bind a `required` select element to `null`, and cause the browser to require users to explicitly select an option before the form can be submitted.

Resolves sveltejs#6126.
@stale
Copy link

stale bot commented Jul 6, 2021

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale-bot label Jul 6, 2021
@theodorejb
Copy link
Contributor Author

The issue is still relevant. Hopefully the PR that addresses it can be reviewed soon.

@stale stale bot removed the stale-bot label Jul 6, 2021
@Conduitry
Copy link
Member

This should be fixed in 3.42.2 - https://svelte.dev/repl/a7fdeec83c504c00b9706edc679330c6?version=3.42.2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants