The Wayback Machine - https://web.archive.org/web/20201130104510/https://github.com/mui-org/material-ui/issues/23708
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

[Autocomplete] Support values other than raw options #23708

Open
dantman opened this issue Nov 25, 2020 · 3 comments
Open

[Autocomplete] Support values other than raw options #23708

dantman opened this issue Nov 25, 2020 · 3 comments

Comments

@dantman
Copy link
Contributor

@dantman dantman commented Nov 25, 2020

  • I have searched the issues of this repository and believe that this is not a duplicate.

Summary 💡

Right now (at least if you use multiple) Autocomplete/useAutocomplete the value that comes back in onChange is always the raw option data. i.e. If you use options=[{value: 'a', label: 'A'}, {value: 'b', label: 'B'}] and select the "A" option then value will be [{value: 'a', label: 'A'}].

Ideally it would be possible to provide multiple=true options=[{value: 'a', label: 'A'}, {value: 'b', label: 'B'}] and get back values like ['a']. Autocomplete does feel like this is intended to be supported, because you can provide a getOptionSelected and it's used to identify selected options instead of doing any comparisons on the option itself. However when it comes to adding an item to a multiple array, all useAutocomplete does is newValue.push(option);. There is no way to control what part of option is actually used as the value.

I think a getOptionValue(option) function would fit the current options implementation the most.

Examples 🌈

<Autocomplete
  multiple
  getOptionSelected={(option, value) => option.value === value}
  getOptionValue={(option) => option.value}
  options={[{value: 'US', label: 'United States', {value: 'CA', label: 'Canada'}]}
  />

Motivation 🔦

MUI's <Select> easily supports this. It uses children instead, so all it takes is options.map(item => <MenuItem key={item.value} value={item.value}>{item.value}</MenuItem>) to do this in Select and pretty much every project I've worked on uses Select this way. It would be strongly preferred if it were easy to use useAutocomplete the same way.

@oliviertassinari
Copy link
Member

@oliviertassinari oliviertassinari commented Nov 25, 2020

@dantman There is a 4th argument to the onChange event that allows handling this problem. It looks like there is an opportunity to better document it. What do you think about this change?

diff --git a/packages/material-ui/src/useAutocomplete/useAutocomplete.d.ts b/packages/material-ui/src/useAutocomplete/useAutocomplete.d.ts
index 0dfea8c799..47be1bd484 100644
--- a/packages/material-ui/src/useAutocomplete/useAutocomplete.d.ts
+++ b/packages/material-ui/src/useAutocomplete/useAutocomplete.d.ts
@@ -250,6 +250,7 @@ export interface UseAutocompleteProps<
    * @param {object} event The event source of the callback.
    * @param {T|T[]} value The new value of the component.
    * @param {string} reason One of "create-option", "select-option", "remove-option", "blur" or "clear".
+   * @param {string} [details]
    */
   onChange?: (
     event: React.SyntheticEvent,

it will then automatically update the API:

diff --git a/docs/pages/api-docs/autocomplete.md b/docs/pages/api-docs/autocomplete.md
index 34caa37fa2..41f18c5120 100644
--- a/docs/pages/api-docs/autocomplete.md
+++ b/docs/pages/api-docs/autocomplete.md
@@ -67,7 +67,7 @@ The `MuiAutocomplete` name can be used for providing [default props](/customizat
 | <span class="prop-name">loadingText</span> | <span class="prop-type">node</span> | <span class="prop-default">'Loading…'</span> | Text to display when in a loading state.<br>For localization purposes, you can use the provided [translations](/guides/localization/). |
 | <span class="prop-name">multiple</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | If `true`, `value` must be an array and the menu will support multiple selections. |
 | <span class="prop-name">noOptionsText</span> | <span class="prop-type">node</span> | <span class="prop-default">'No options'</span> | Text to display when there are no options.<br>For localization purposes, you can use the provided [translations](/guides/localization/). |
-| <span class="prop-name">onChange</span> | <span class="prop-type">func</span> |  | Callback fired when the value changes.<br><br>**Signature:**<br>`function(event: object, value: T \| T[], reason: string) => void`<br>*event:* The event source of the callback.<br>*value:* The new value of the component.<br>*reason:* One of "create-option", "select-option", "remove-option", "blur" or "clear". |
+| <span class="prop-name">onChange</span> | <span class="prop-type">func</span> |  | Callback fired when the value changes.<br><br>**Signature:**<br>`function(event: object, value: T \| T[], reason: string, details?: string) => void`<br>*event:* The event source of the callback.<br>*value:* The new value of the component.<br>*reason:* One of "create-option", "select-option", "remove-option", "blur" or "clear". |
 | <span class="prop-name">onClose</span> | <span class="prop-type">func</span> |  | Callback fired when the popup requests to be closed. Use in controlled mode (see open).<br><br>**Signature:**<br>`function(event: object, reason: string) => void`<br>*event:* The event source of the callback.<br>*reason:* Can be: `"toggleInput"`, `"escape"`, `"select-option"`, `"remove-option"`, `"blur"`. |
 | <span class="prop-name">onHighlightChange</span> | <span class="prop-type">func</span> |  | Callback fired when the highlight option changes.<br><br>**Signature:**<br>`function(event: object, option: T, reason: string) => void`<br>*event:* The event source of the callback.<br>*option:* The highlighted option.<br>*reason:* Can be: `"keyboard"`, `"auto"`, `"mouse"`. |
 | <span class="prop-name">onInputChange</span> | <span class="prop-type">func</span> |  | Callback fired when the input value changes.<br><br>**Signature:**<br>`function(event: object, value: string, reason: string) => void`<br>*event:* The event source of the callback.<br>*value:* The new value of the text input.<br>*reason:* Can be: `"input"` (user input), `"reset"` (programmatic change), `"clear"`. |

However, in your case, the simplest is probably to store the value as-is, and derive it with a map when you need to transform it. Why not use this approach?

@oliviertassinari oliviertassinari changed the title [Autocomplete:multiple] Support values other than raw options [Autocomplete] Support values other than raw options Nov 25, 2020
@dantman
Copy link
Contributor Author

@dantman dantman commented Nov 25, 2020

Forcing the developer to manually manage the values list doesn't feel like a great alternative. Using Select, TextField, etc... isn't this complex even when using the {value, label} pattern.

However, in your case, the simplest is probably to store the value as-is, and derive it with a map when you need to transform it. Why not use this approach?

You mean store country: [{value: 'US', label: 'United States'}, {value: 'CA', label: 'Canada'}] in the form values when US+CA are selected and countries.map(country => country.value) it when doing an api call?

Honestly it feels wrong to have localized labels in form data just because of limitations in useAutocomplete's implementation. And MUI doesn't work this way with any other field. Select, Radio, and Checkbox can easily be connected to form handling code and all support using a separate value and label for options. It doesn't feel right that if you have a Select implemented that way (with the same simple submit handler code as the rest of your fields) and it becomes too long to be a normal select, you have to rewrite the submit handler code to work differently than every other field, just because the field type used changed from Select to Autocomplete.

@oliviertassinari
Copy link
Member

@oliviertassinari oliviertassinari commented Nov 25, 2020

We have a similar pain in this direction on #21489. Ok, why not. we could:

  • use getOptionValue to feed the default behavior of getOptionSelected.
  • use getOptionValue to later implement native integration with a <form>.

Feel free to work on it. At least, we have spotted a small issue with the generated documentation.

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

Successfully merging a pull request may close this issue.

None yet
2 participants
You can’t perform that action at this time.