Repository: mui/material-ui (TypeScript)
Date: 2020-02-14 13:47:26+00:00
Task type: Bug Fix

Issue:
[Autocomplete] Warn wrong comparison getOptionSelected implementation 
Bug Demo: https://codesandbox.io/s/material-demo-q3fno

![autocomplete](https://user-images.githubusercontent.com/30294752/73972400-d7be0080-4931-11ea-8d5f-40c8499d8752.png)




Issue discussion:
It's a duplicate of #18499.
@tagaychinova Interesting case, I propose the following new error message:

```diff
diff --git a/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js b/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
index dfd6171c8..47e2a4c59 100644
--- a/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
+++ b/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
@@ -439,6 +439,24 @@ export default function useAutocomplete(props) {
       const item = newValue;
       newValue = Array.isArray(value) ? [...value] : [];

+      if (process.env.NODE_ENV !== 'production') {
+        let matches = 0;
+        for (let i = 0; i < newValue.length; i += 1) {
+          if (getOptionSelected(item, newValue[i])) {
+            matches += 1;
+          }
+        }
+
+        if (matches > 1) {
+          console.error(
+            [
+              'Material-UI: the `getOptionSelected` method of useAutocomplete do not handle the arguments correctly.',
+              `The component expects a single value to match a given option but found ${matches}.`,
+            ].join('\n'),
+          );
+        }
+      }
+
       const itemIndex = findIndex(newValue, valueItem => getOptionSelected(item, valueItem));

       if (itemIndex === -1) {
```
@oliviertassinari why not we try to solve this issue instead of giving error message?
@ahmad-reza619 This error message is a lead/tip on how to solve the problem in your codebase. If you can't figure it out with it, then this warning might not be good enough. I wonder what lead to this wrong usage of the API and how we could better help 🤔. 
Maybe we should rename `getOptionSelected` to `optionEqualValue` or something similar 🤔. 
> Maybe we should rename `getOptionSelected` to `optionEqualValue` or something similar 🤔.

sounds good, and btw why not replace the `for` with `Array.prototype.some` for edgy fp style 😎 
something like this

```javascript
const matches = newValue.some(val => optionEqualValue(item, val));

if (matches) {
   console.error(
        [
            'Material-UI: the `getOptionSelected` method of useAutocomplete do not handle the arguments correctly.',
            `The component expects a single value to match a given option but found ${matches}.`,
        ].join('\n')
   )
}
```
> console.error

I'd prefer a warning here and I'm not even sure there aren't valid use cases for this e.g. fallbacks.

And I'm not sure why this should warn. What is wrong with the provided codesandbox? It seems like a bug on our side to me.
@ahmad-reza619 What about IE 11?
@eps1lon makes me think of a previous discussion we didn't resolve. When should we use error vs warn? 

Ok, here is the solution. The `getOptionSelected` method accepts two arguments. A value and an option. Its purpose is to signal when two objects from these ensembles are identical. We use it to construct a function that takes an option and maps it to the corresponding value. You can't say option A points to all the values. 
> You can't say option A points to all the values.

How does this apply here? Each option and current value is unique in the codesandbox.

>Ok, here is the solution. The getOptionSelected method accepts two arguments.

Tried that in the codesandbox. The second parameter is undefined.
I hope it will be clearer with https://codesandbox.io/s/material-demo-b3nsz.
```diff
-getOptionSelected={option => value.find(v => v.id === option.id)}
+getOptionSelected={(option, value) => value.id === option.id}
```
@oliviertassinari Ok sorry for misunderstanding, but looks like Array.prototype.some is actually supported in IE based on [this](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some) MDN article.

But well yeah it only return true if there is one of its predicate is true 🤔 
Can i give it a go?
Perfect for IE 11 :) but I think that it could be interesting display the number of matches. 
Honestly, I'm not sure the warning will be enough. It took me a good chunk of time to figure out what was wrong with your example. Considering I have myself designed most of the component, I can only imagine how it can be confusing for somebody else.
@eps1lon what do you think about changing the name of the prop? 

Solution patch:
diff --git a/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js b/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
--- a/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
+++ b/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
@@ -439,6 +439,21 @@ export default function useAutocomplete(props) {
       const item = newValue;
       newValue = Array.isArray(value) ? [...value] : [];
 
+      if (process.env.NODE_ENV !== 'production') {
+        const matches = newValue.filter(val => getOptionSelected(item, val));
+
+        if (matches.length > 1) {
+          console.error(
+            [
+              'Material-UI: the `getOptionSelected` method of useAutocomplete do not handle the arguments correctly.',
+              `The component expects a single value to match a given option but found ${
+                matches.length
+              } matches.`,
+            ].join('\n'),
+          );
+        }
+      }
+
       const itemIndex = findIndex(newValue, valueItem => getOptionSelected(item, valueItem));
 
       if (itemIndex === -1) {


Test patch:
diff --git a/packages/material-ui-lab/src/Autocomplete/Autocomplete.test.js b/packages/material-ui-lab/src/Autocomplete/Autocomplete.test.js
--- a/packages/material-ui-lab/src/Autocomplete/Autocomplete.test.js
+++ b/packages/material-ui-lab/src/Autocomplete/Autocomplete.test.js
@@ -736,6 +736,36 @@ describe('<Autocomplete />', () => {
         'For the input option: "a", `getOptionLabel` returns: undefined',
       );
     });
+
+    it('warn if getOptionSelected match multiple values for a given option', () => {
+      const value = [{ id: '10', text: 'One' }, { id: '20', text: 'Two' }];
+      const options = [
+        { id: '10', text: 'One' },
+        { id: '20', text: 'Two' },
+        { id: '30', text: 'Three' },
+      ];
+
+      render(
+        <Autocomplete
+          {...defaultProps}
+          multiple
+          options={options}
+          value={value}
+          getOptionLabel={option => option.text}
+          getOptionSelected={option => value.find(v => v.id === option.id)}
+          renderInput={params => <TextField {...params} autoFocus />}
+        />,
+      );
+
+      fireEvent.keyDown(document.activeElement, { key: 'ArrowDown' });
+      fireEvent.keyDown(document.activeElement, { key: 'ArrowDown' });
+      fireEvent.keyDown(document.activeElement, { key: 'Enter' });
+
+      expect(consoleErrorMock.callCount()).to.equal(1); // strict mode renders twice
+      expect(consoleErrorMock.args()[0][0]).to.include(
+        'The component expects a single value to match a given option but found 2 matches.',
+      );
+    });
   });
 
   describe('prop: options', () => {