mui / material-ui

Material UI: Comprehensive React component library that implements Google's Material Design. Free forever.
https://mui.com/material-ui/
MIT License
93.75k stars 32.24k forks source link

[Autocomplete] 'renderTags' ignored when 'multiple' is false #26440

Open Idonoghue opened 3 years ago

Idonoghue commented 3 years ago

Current Behavior 😯

On v5 alpha, when passing renderTags to Autocomplete when multiple is false the renderTags prop will be ignored.

Expected Behavior πŸ€”

renderTags should be used to render the selected item(s) in the Input no matter what multiple is set to.

Steps to Reproduce πŸ•Ή

Comment line 11 of this code sandbox

Context πŸ”¦

We have a single select Autocomplete where we set the renderOption prop to render a custom element that includes a small profile pic. However once one option is selected there is currently no way to render this custom element in the Input of the Autocomplete

Your Environment 🌎

`npx @material-ui/envinfo` ``` System: OS: Linux 5.8 Pop!_OS 20.04 LTS Binaries: Node: 14.15.1 - ~/.nvm/versions/node/v14.15.1/bin/node Yarn: 1.22.5 - /usr/bin/yarn npm: 6.14.10 - ~/Projects/clinical-dashboard-v2/node_modules/.bin/npm Browsers: Chrome: 89.0.4389.82 <--- using this one Firefox: 86.0 npmPackages: @emotion/react: ^11.0.0 => 11.1.4 @emotion/styled: ^11.0.0 => 11.0.0 @material-ui/core: 5.0.0-alpha.34 => 5.0.0-alpha.34 @material-ui/lab: 5.0.0-alpha.34 => 5.0.0-alpha.34 @material-ui/private-theming: 5.0.0-alpha.33 @material-ui/styled-engine: 5.0.0-alpha.34 @material-ui/styles: 5.0.0-alpha.33 @material-ui/system: 5.0.0-alpha.34 @material-ui/types: 6.0.0 @material-ui/unstyled: 5.0.0-alpha.34 @material-ui/utils: 5.0.0-alpha.33 @types/react: ^17.0.3 => 17.0.3 react: ^17.0.1 => 17.0.1 react-dom: ^17.0.1 => 17.0.1 styled-components: ^5.2.1 => 5.2.1 typescript: ^4.2.0 => 4.2.4 ```
Idonoghue commented 3 years ago

I also wonder if this was perhaps intentional. Regardless, I believe there is enough of a use case to either allow renderTags to work with multiple={false} or add some other prop to override the display of a single selected option.

I'm curious what your workaround was @degzhaus. Were you able to do it somehow inside the renderInput prop?

eps1lon commented 3 years ago

Thanks for the feedback.

What we consider a "tag" is a combination of a value and an affordance to de-select that value. There's no need to de-select a single value in a single-select Autocomplete so we don't even attempt to "render tags".

The purpose of renderTags is not really to display a different label. That's what getOptionLabel is for.

So we definitely do lack some clearer documentation for the Autocomplete.

As to your specific issue: Could you explain a bit more what UI you're trying to build? Then we can discuss what kind of API could solve this problem.

Idonoghue commented 3 years ago

Thanks for the response @eps1lon . That makes a lot of sense. Looking at it now everything inside the getTagProps passed to the renderTags function wouldn't apply to a single select.

To describe a bit further our use case, we have a custom component, <EntityLabel/> that renders formatted text with a profile pic. It is trivially easy to render these components as the list of options for the Autocomplete using renderOption. However once one is selected and displayed in the input, there is no render- prop in the Autocomplete API for a custom component like there is for multiple selected values.

kgrhartlage commented 3 years ago

This would be an incredibly helpful change for us also. We need to show a device status icon - and ideally also a device class icon - not only on the options, but also for the selected value. We use this type of device select for both single and multiselection. IIUC one can only render a string unless multiselection is enabled, which results in an inconsistent UI in our case. And since we cannot omit the status icon, we currently add it to the textfield label, which is of course very undesirable.

screenshots ![CleanShot 2021-08-09 at 16 08 26](https://user-images.githubusercontent.com/42832087/128721186-31d525c0-0123-4aac-8356-ae864b692a40.png) ![CleanShot 2021-08-09 at 16 24 53](https://user-images.githubusercontent.com/42832087/128722483-c2a8a1fc-7e04-4c7f-85f9-e6a6f484a7e1.png)
Harsha99unifo commented 3 years ago

I am using Autocomplete to display the selected popup as chip and typed text as normal text

G1itcher commented 2 years ago

This is something that would be very helpful for us also

ZeeshanTamboli commented 2 years ago

I can think of the following approaches:

  1. We could have a renderTag or renderSingleValue API prop for this.
  2. We can have a components prop with SingleValue slot like what react-select does in https://codesandbox.io/s/s0yc5?module=/example.tsx&file=/example.tsx.

We should also update the API docs for renderTags that it works only when multiple is true.

@oliviertassinari @michaldudak What do you guys think?

yurivasconcelos commented 2 years ago

+1 for this feature!

michaldudak commented 2 years ago

I see how this may be useful.

A thing to note, though, is that rendering anything else than a default string will break the current autocompletion logic.

Let's say we have a list of colors and when selected, we display a swatch before the color name in the textbox:

πŸŸ₯ red
--------
red
green
blue

When we now focus the textbox and press Backspace, we are left with πŸŸ₯ re, which does not match any of the options.

We could overcome this by disallowing to edit the selected value and clearing the input completely when Backspace, Delete, or any character key is pressed, similar to how react-select does it. This logic would kick in only if we override what is displayed in the textbox. Alternatively, we could display the custom value only if the textbox is not focused. Doing so would let us leave the current autocompletion logic.

Do you have any preferences in this matter?

cc @mnajdova

ggmartins091 commented 2 years ago

+1 for this! We would also be helped a lot if this was a feature.

Edit: found out our team is actually using this approach: https://codesandbox.io/s/material-demo-forked-ijzbt?file=/demo.js

degzhaus' suggestion didn't work but the one used on this csb did. Basically adding ...params.InputProps to the TextField.

humarkx commented 2 years ago

If we want to show a single value as we have multiple tags, what would be the approach?

Also, another idea would be to have limitValues and instead of limitTags that hide the values, it would always allow only 1, if 1 other value were selected it would replace it.

@eps1lon @michaldudak Any news on this issue?

ggmartins091 commented 2 years ago

@michaldudak

Aren't we be able to keep the actual value somewhere else and have the rendered component not interfere with our autocompletion?

Based on your suggestion and example, when deleting the character "d", could we make it so that the swatch goes away?

Idk if I'm going to far here, but basically having a way to map a value to its renderTag, so "red" maps to "πŸŸ₯ red", as soon as we deleted a single character from the input, there won't be any value that maps to that, so we go back to normal rendering the text typed.

So instead of this

 πŸŸ₯ re
 --------
 red
 green
 blue

We would get this: since that the tag "πŸŸ₯ re" doesn't have any value mapped to it, (not a valid option)

re
--------
red
green
blue

-edit ps. I dont know the inner workings of the AutoComplete component, so maybe I'm just talking non sense here, also maybe this approach is too expensive.

Idonoghue commented 2 years ago

From a UI POV I personally prefer the latter solution where backspace removes the selected value and completely clears the input. This aligns with Autocomplete behavior when multiple={true}. Once an item has been selected a single backspace to remove selected item feels intuitive and desired.

The case where users instead want to view similar items to the one they just removed (search results for 're') seems less likely. One caveat to that may be if freeSolo={true}? I struggle to think of a real world use case where renderTags and freeSolo are used together.

yuklai commented 2 years ago

The gmail to field would be something that supports renderTags and freeSolo={true}?

michaldudak commented 1 year ago

Aren't we be able to keep the actual value somewhere else and have the rendered component not interfere with our autocompletion?

Based on your suggestion and example, when deleting the character "d", could we make it so that the swatch goes away?

Idk if I'm going to far here, but basically having a way to map a value to its renderTag, so "red" maps to "πŸŸ₯ red", as soon as we deleted a single character from the input, there won't be any value that maps to that, so we go back to normal rendering the text typed.

I think it could be confusing if the rendered value changed during editing.

I'm leaning towards a solution similar to how the Autocomplete works in multiple mode, but as @Idonoghue noted, there are rough edges here as well.

If someone is willing to prototype a solution, I'll be happy to review it and discuss the solutions for problems that arise.

prajeeshbs commented 9 months ago

Is there any solution for this? The workaround that i am doing now is to add a startAdornment in the textfield and display whatever is needed. Then i hide the label of the autocomplete component using getOptionLabel: () => "",

emil-datcu commented 8 months ago

Yo :)

I would need this 'feature' as well... I just want to be able to style the displayed option like I'm able to style it for the multiple=true. My example 'need' would look like this:

image

If this would cause too much of an overhead for the autocomplete component, does anyone has any other workaround that could be used to achieve something like this or it's just impossible for the single autocomplete to have the displayed value styled any other way?

Thanks πŸ€™

clxy commented 7 months ago

My workaround/idea is

  1. Keep the origin value of multiple, and set it to true anyway.
  2. And change the new value to the last element of selected values in onChange when origin multiple is false.
... 
const { multiple, ...props } = p
...
<MuiAutocomplete
      multiple={true}
      onChange={(e, d) => {
        const newValue = multiple ? d : Array.from(d).slice(-1)
        props.onChange?.call(null, e, newValue)
      }}
     ...
     {... props}

Still, I’m waiting for the official solution as well.

Liam26 commented 4 months ago

Any news on an official solution for this? Would be extremely helpful and it's been 3 years :pray:

Liam26 commented 4 months ago

Yo :)

I would need this 'feature' as well... I just want to be able to style the displayed option like I'm able to style it for the multiple=true. My example 'need' would look like this: image

If this would cause too much of an overhead for the autocomplete component, does anyone has any other workaround that could be used to achieve something like this or it's just impossible for the single autocomplete to have the displayed value styled any other way?

Thanks πŸ€™

Did you ever find a workaround for this?, your exact case is what i'm trying to do I want to have an autocomplete field that is a single select rather than allowing multiple but I want to style it to match my multiple selects by having the single selected option look like a chip as well.