[autocomplete] Support values other than raw options - #49078
[autocomplete] Support values other than raw options#49078silviuaavram wants to merge 12 commits into
Conversation
Deploy previewBundle size
Check out the code infra dashboard for more information about this PR. |
There was a problem hiding this comment.
🟡 Changes recommended
Critical mapped-label resolution defects and moderate prop-forwarding and validation issues remain unresolved.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds primitive mapped values to Autocomplete and useAutocomplete through getOptionValue.
Changes:
- Extends runtime behavior and TypeScript inference for mapped values.
- Adds validation, equality handling, and regression/type tests.
- Preserves existing option-object behavior when mapping is omitted.
File summaries
| File | Review |
|---|---|
packages/mui-material/src/useAutocomplete/utils/validateOptionValues.ts |
Validation can crash for repeated invalid mapper outputs. |
packages/mui-material/src/useAutocomplete/useAutocomplete.test.js |
Adds runtime coverage for mapping, equality, and validation. |
packages/mui-material/src/useAutocomplete/useAutocomplete.spec.ts |
Adds hook type coverage. |
packages/mui-material/src/useAutocomplete/useAutocomplete.js |
Mapped values are not consistently resolved before label callbacks. |
packages/mui-material/src/useAutocomplete/useAutocomplete.d.ts |
Documented mapped-value flows still pass values directly to option-facing callbacks. |
packages/mui-material/src/Autocomplete/Autocomplete.spec.tsx |
Adds component type coverage. |
packages/mui-material/src/Autocomplete/Autocomplete.d.ts |
Declared prop is forwarded to the DOM instead of being consumed by the component. |
Review details
Suppressed comments (2)
packages/mui-material/src/useAutocomplete/useAutocomplete.js:144
getOptionValueis only applied during equality checks.selectNewValuestill assigns/pushes the raw option (lines 808 and 829), so selecting an unselected option makesonChangeand internal state contain the option object rather than the declared mapped primitive (for example,['foo', options[1]]instead of['foo', 'bar']). Map option-origin selections before callinghandleValue, while leaving free-solo strings unchanged.
return getOptionValue(option) === value2;
packages/mui-material/src/useAutocomplete/useAutocomplete.js:144
- In multiple free-solo mode,
selectNewValuecalls this comparator with the newly typed string as theoptionargument while checking existing values. That forwards a free-solo string togetOptionValue(or to the custom comparator'sOptionparameter), contrary to the new callback contract and can throw for object-specific mappers. Handle free-solo duplicate/toggle logic without invoking option-only callbacks.
if (isOptionEqualToValueProp) {
return isOptionEqualToValueProp(option, value2);
}
return getOptionValue(option) === value2;
- Files reviewed: 5/7 changed files
- Comments generated: 2
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
🟡 Changes recommended
Critical free-solo collision and mapped-value typing issues remain unresolved.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 14/16 changed files
- Comments generated: 1
- Review effort level: Balanced
Summary
Fixes #23708 by adding a
getOptionValueprop toAutocompleteanduseAutocomplete.getOptionValuelets consumers store a primitive option identifier invalue,defaultValue, andonChangeinstead of storing the complete option object.Without
getOptionValue, the existing object-value behavior remains unchanged.API behavior
When
getOptionValueis provided:valueanddefaultValuecontain the value returned bygetOptionValue.onChangereceives the mapped value.renderValuereceives the mapped value.isOptionEqualToValuereceives the original option as its first argument and the mapped value as its second argument.getOptionLabel,getOptionDisabled,getOptionKey, andrenderOptioncontinue to receive the original option.onChangedetails continue to expose the original option when one is available.The returned option value must be a unique, non-null primitive:
string,number,bigint, orboolean.Development-time validation reports invalid and duplicate option values.
Internal flow
Options are converted to their external values only when an option selection is committed:
The hook memoizes the inverse lookup needed to resolve controlled mapped values back to their original options.
This resolution is used before calling option-facing APIs, including when:
When
isOptionEqualToValueis provided, it defines how a mapped value is resolved to an option. Otherwise, a memoized map provides constant-time lookup.useAutocompletealso exposesgetOptionFromValueso wrappers such asAutocompletecan perform the same resolution without duplicating the map or matching logic.freeSolovaluesValues created from free-solo input bypass
getOptionValue.For example, with a numeric option mapping:
Selecting an option produces
1or2, while entering custom text produces the entered string.This preserves the existing contract that free-solo values are strings and prevents free-solo text from being passed to an option-only callback.
String collisions in
freeSoloA mapped string value and a free-solo string cannot be distinguished after they enter the controlled value API.
For example:
The value
"draft"could mean either:"draft".Previously, mapped option lookup took precedence. As a result, committing the free-solo text
"draft"could immediately resolve to thePublishedoption, replace the input label, render the wrong chip label, and mark that option as selected.Because controlled values do not retain their origin, this ambiguity cannot be resolved reliably from the string alone.
The API therefore uses the following explicit policy:
freeSoloandgetOptionValueare used together.getOptionValuemust return anumber,bigint, orbooleaninfreeSolomode.getOptionValuewhenfreeSolocan be enabled.isOptionEqualToValuecallbacks cannot override the free-solo string distinction.String mappings remain supported when
freeSolois disabled.TypeScript
The mapped value type is inferred from
getOptionValue:Multiple selection produces an array:
In
freeSolo, the mapped value must be non-string so that the resulting union remains distinguishable:Tests
Added coverage for:
getOptionValue.freeSolo.Documentation and generated API descriptions were also updated with the mapped-value and
freeSoloconstraints.