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] Add ability to render custom single value #45387

Open
wants to merge 33 commits into
base: master
Choose a base branch
from

Conversation

ZeeshanTamboli
Copy link
Member

@ZeeshanTamboli ZeeshanTamboli commented Feb 24, 2025

Closes #26440

Based on the upvotes and requests in #26440, this PR introduces a new renderSingleValue prop to customize the display of the selected value. This is separate from renderTags for clarity. I avoided naming it renderTag to prevent potential confusion and typos.

Preview: https://deploy-preview-45387--material-ui.netlify.app/material-ui/react-autocomplete/#custom-single-value-rendering

@ZeeshanTamboli ZeeshanTamboli added the component: autocomplete This is the name of the generic UI component, not the React module! label Feb 24, 2025
@mui-bot
Copy link

mui-bot commented Feb 24, 2025

Netlify deploy preview

@material-ui/core: parsed: +0.07% , gzip: +0.09%
Autocomplete: parsed: +0.23% , gzip: +0.26%

Bundle size report

Details of bundle changes (Toolpad)
Details of bundle changes

Generated by 🚫 dangerJS against 934e57d

@zannager zannager requested a review from DiegoAndai February 24, 2025 14:47
@ZeeshanTamboli ZeeshanTamboli removed the request for review from DiegoAndai February 24, 2025 14:53
@ZeeshanTamboli ZeeshanTamboli marked this pull request as draft February 24, 2025 14:54
@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged label Feb 25, 2025
@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged label Feb 25, 2025
@ZeeshanTamboli ZeeshanTamboli added the package: material-ui Specific to @mui/material label Feb 26, 2025
@ZeeshanTamboli ZeeshanTamboli added the new feature New feature or request label Feb 27, 2025
@ZeeshanTamboli ZeeshanTamboli marked this pull request as ready for review February 27, 2025 12:47
@ZeeshanTamboli ZeeshanTamboli changed the title [Autocomplete] Render tags even if multiple is false [Autocomplete] Add ability to render custom single value Feb 27, 2025
Copy link
Member

@DiegoAndai DiegoAndai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @ZeeshanTamboli, thanks for working on this!

I think the solution is the correct one but I'm wondering what's the best for the API design, the options I see:

  • This PR's solution: add a renderSingleValue prop which is only used when multiple={false}
  • Add a renderValue prop which works regardless of multiple, and deprecate renderTags in favor of it
  • Use slots, like slots.value and slotProps.value
  • Other

What do you think @aarongarciah?

@@ -513,6 +514,7 @@ const Autocomplete = React.forwardRef(function Autocomplete(inProps, ref) {
focusedTag,
anchorEl,
setAnchorEl,
tagProps,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useAutocomplete doesn't return tagProps, did you mean getTagProps?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It now returns in this PR for a single tag: https://github.com/mui/material-ui/pull/45387/files#diff-a3e15b1f22a8ed2de23c8cee46ea92a1d2828d4718cb471c6404d9baadd2c68fR1154-R1158. Should I rename it to singleTagProps? I avoided get* since it doesn’t need to be a function.

@ZeeshanTamboli
Copy link
Member Author

Hey @ZeeshanTamboli, thanks for working on this!

I think the solution is the correct one but I'm wondering what's the best for the API design, the options I see:

  • This PR's solution: add a renderSingleValue prop which is only used when multiple={false}
  • Add a renderValue prop which works regardless of multiple, and deprecate renderTags in favor of it
  • Use slots, like slots.value and slotProps.value
  • Other

I wouldn’t use the slots API since this is passed as startAdornment in the TextField. Also, IMO, combining single and multiple tags in renderValue isn’t ideal—we wouldn’t know if selecting another option should create a new tag (multiple) or replace the existing one (single) unless user passes some config.

@aarongarciah
Copy link
Member

I wouldn’t use the slots API since this is passed as startAdornment in the TextField. Also, IMO, combining single and multiple tags in renderValue isn’t ideal—we wouldn’t know if selecting another option should create a new tag (multiple) or replace the existing one (single) unless user passes some config.

@ZeeshanTamboli can you provide a code snippet of how it'd look like if we combined rendering single and multiple values in a single prop like renderValue to highlight the problem you're describing? I'm not sure I fully understand it.

@ZeeshanTamboli
Copy link
Member Author

ZeeshanTamboli commented Mar 6, 2025

@ZeeshanTamboli can you provide a code snippet of how it'd look like if we combined rendering single and multiple values in a single prop like renderValue to highlight the problem you're describing? I'm not sure I fully understand it.

@aarongarciah Check out the existing renderTags and the newly introduced renderSingleValue in this PR:

  • renderTags (for multiple values):
    renderTags={(tagValue, getTagProps) =>
      tagValue.map((option, index) => {
        const { key, ...tagProps } = getTagProps({ index });
        return (
          <Chip
            key={key}
            label={option.title}
            {...tagProps}
          />
        );
      })
    }
  • renderSingleValue (for a single value):
    renderSingleValue={(value, singleTagProps) => (
      <Chip label={value.title} {...singleTagProps} />
    )}

If we were to combine them into a single renderValue prop, it would introduce complexity:

  1. The values parameter would need to handle both single values (string/object) and multiple values (array).
  2. In TypeScript, developers would need to narrow down the type first to manually distinguish between one of the two cases:
renderValue={(values, getTagProps) => {
  if (Array.isArray(values)) {
    return values.map((val, index) => {
      const { key, ...tagProps } = getTagProps({ index });
      return <Chip key={key} label={val.title} {...tagProps} />;
    });
  }
  return <Chip label={values.title} />;
}}
  1. For multiple values, users must pass the current index to getTagProps, while for a single value, no index is needed.

I think it's possible but adds complexity. To avoid point 2 above, we'd need a conditional generic type based on multiple={true} and add logic everywhere based on multiple being present or not to pass either an array or a single value to renderValue. What do you think?

@aarongarciah
Copy link
Member

@ZeeshanTamboli thanks for the explanation. What I find weird is that users can use renderSingleValue and return a string but get a totally different behavior than using getOptionLabel: the former injects something in the startAdornment while the latter is the input value. I think the renderSingleValue name can cause confusion.

Although there's no concept of "tags" in single selection mode by default in the Autocomplete (i.e. single values are represented as plain text in the input), I think renderTags and renderSingleValue are doing the same thing: rendering "rich" content for the value/s. Having two separate props feels odd.

Would it make sense for renderTags to always provide an array as the first argument no matter if the Autocomplete is in single or multiple mode so users could always use this prop? Would it be weird? renderTags would take precedence in single mode over getOptionLabel.

I have doubts about the best path forward. Let's not rush this since it's important to keep the Autocomplete API at a minimum (it's large enough already) and a workaround exists (not ideal though).

@DiegoAndai
Copy link
Member

Would it make sense for renderTags to always provide an array as the first argument no matter if the Autocomplete is in single or multiple mode so users could always use this prop? Would it be weird? renderTags would take precedence in single mode over getOptionLabel.

This idea makes sense to me. I would rename it to renderValue and deprecate renderTags in favor of it though.

@ZeeshanTamboli
Copy link
Member Author

I'll try it out. Let's see how it pans out.

@ZeeshanTamboli
Copy link
Member Author

ZeeshanTamboli commented Mar 8, 2025

@aarongarciah @DiegoAndai Based on the discussion, the renderValue API would work like this:

For single value, when multiple isn't provided:

renderValue={(tagValue, getTagProps) => (
  <Chip label={tagValue[0].title} {...getTagProps()} />
)}

Note that label is accessed from tagValue[0] since value is always an array, even for single selection, and index isn't needed for getTagProps.


For multiple values (same as before):

renderValue={(tagValue, getTagProps) =>
  tagValue.map((option, index) => (
    <Chip
      key={getTagProps({ index }).key}
      label={option.title}
      {...getTagProps({ index })}
    />
  ))
}

Should I go ahead and make the changes?

@aarongarciah
Copy link
Member

@ZeeshanTamboli looks good! I wonder if it should be called renderValues instead, since the value is always an array. Also, I think we need to come up with a better name in our examples for tagValue / getTagProps. Maybe item / getItemProps works? cc @DiegoAndai

Also cc @michaldudak in case you have some feedback.

@michaldudak
Copy link
Member

michaldudak commented Mar 11, 2025

I wonder if it wouldn't be possible to have the type of the parameter depend on the Autocomplete's multiple prop (so it's an array only when multiple = true). The props already are generic over Multiple, so it should be doable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: autocomplete This is the name of the generic UI component, not the React module! new feature New feature or request package: material-ui Specific to @mui/material
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Autocomplete] 'renderTags' ignored when 'multiple' is false
5 participants