Repository: mui/material-ui (TypeScript)
Date: 2019-10-31 08:12:37+00:00
Task type: Bug Fix

Issue:
[AutoComplete] Multiple + freeSolo crash
I was playing around with the Autocomplete component in codesandbox to see if it would be worth switching to from react-select.  Unfortunately, I can very reliably trigger a page crash due to an error occurring in the <ForwardRef(Autocomplete)> component.

<!-- Checked checkbox should look like this: [x] -->

- [ x] The issue is present in the latest release.
- [x ] I have searched the [issues](https://github.com/mui-org/material-ui/issues) of this repository and believe that this is not a duplicate.

## Current Behavior 😯

<ForwardRef(Autocomplete)> component throws an error almost all of the time.

## Expected Behavior 🤔

The box's menu opens and closes without error

## Steps to Reproduce 🕹

https://codesandbox.io/s/material-demo-hwcj7

Steps:

1.   In the codebox sample, click on the box so its menu opens
2.  Click off of the box so that it loses focus rather than selecting an option
3.  Click back on the box to attempt to open its menu
4.  ForwardRef error occurs!

## Context 🔦

Now that Material UI is building their own AutoComplete component, I would rather use it than react-select.



Issue discussion:
@ideoclickVanessa Thanks for the report! I assure you, it's not the only way to crash the component. In a week or two, it will be more reliable 💪. You can follow our progress with https://github.com/mui-org/material-ui/labels/component%3A%20Autocomplete.
What do you think of this diff?
```diff
diff --git a/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js b/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
index bc02f10f1..7f28ddca2 100644
--- a/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
+++ b/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
@@ -163,14 +163,19 @@ export default function useAutocomplete(props) {
   const [focused, setFocused] = React.useState(false);

   const resetInputValue = useEventCallback(newValue => {
-    setInputValue(newValue != null ? getOptionLabel(newValue) : '');
+    let newInputValue;
+    if (multiple) {
+      newInputValue = '';
+    } else {
+      newInputValue = newValue != null ? getOptionLabel(newValue) : '';
+    }
+
+    setInputValue(newInputValue);
   });

   React.useEffect(() => {
-    if (!multiple) {
-      resetInputValue(value);
-    }
-  }, [value, multiple, resetInputValue]);
+    resetInputValue(value);
+  }, [value, resetInputValue]);

   const { current: isOpenControlled } = React.useRef(openProp != null);
   const [openState, setOpenState] = React.useState(false);
@@ -375,11 +380,7 @@ export default function useAutocomplete(props) {
       handleClose(event);
     }

-    if (multiple) {
-      setInputValue('');
-    } else {
-      setInputValue(getOptionLabel(newValue));
-    }
+    resetInputValue(newValue);

     selectedIndexRef.current = -1;
   };
@@ -550,7 +551,7 @@ export default function useAutocomplete(props) {
     if (autoSelect && selectedIndexRef.current !== -1) {
       handleValue(event, filteredOptions[selectedIndexRef.current]);
     } else if (!freeSolo) {
-      setInputValue((value && getOptionLabel(value)) || '');
+      resetInputValue(value);
     }

     handleClose(event);
```

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
@@ -163,14 +163,19 @@ export default function useAutocomplete(props) {
   const [focused, setFocused] = React.useState(false);
 
   const resetInputValue = useEventCallback(newValue => {
-    setInputValue(newValue != null ? getOptionLabel(newValue) : '');
+    let newInputValue;
+    if (multiple) {
+      newInputValue = '';
+    } else {
+      newInputValue = newValue != null ? getOptionLabel(newValue) : '';
+    }
+
+    setInputValue(newInputValue);
   });
 
   React.useEffect(() => {
-    if (!multiple) {
-      resetInputValue(value);
-    }
-  }, [value, multiple, resetInputValue]);
+    resetInputValue(value);
+  }, [value, resetInputValue]);
 
   const { current: isOpenControlled } = React.useRef(openProp != null);
   const [openState, setOpenState] = React.useState(false);
@@ -375,11 +380,7 @@ export default function useAutocomplete(props) {
       handleClose(event);
     }
 
-    if (multiple) {
-      setInputValue('');
-    } else {
-      setInputValue(getOptionLabel(newValue));
-    }
+    resetInputValue(newValue);
 
     selectedIndexRef.current = -1;
   };
@@ -550,7 +551,7 @@ export default function useAutocomplete(props) {
     if (autoSelect && selectedIndexRef.current !== -1) {
       handleValue(event, filteredOptions[selectedIndexRef.current]);
     } else if (!freeSolo) {
-      setInputValue((value && getOptionLabel(value)) || '');
+      resetInputValue(value);
     }
 
     handleClose(event);


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
@@ -1,16 +1,17 @@
 import React from 'react';
+import { expect } from 'chai';
 import { createMount, getClasses } from '@material-ui/core/test-utils';
 import describeConformance from '@material-ui/core/test-utils/describeConformance';
-// import { cleanup, createClientRender } from 'test/utils/createClientRender';
-import { cleanup } from 'test/utils/createClientRender';
+import { createClientRender, fireEvent } from 'test/utils/createClientRender';
+import TextField from '@material-ui/core/TextField';
 import Autocomplete from './Autocomplete';
 
 describe('<Autocomplete />', () => {
   let mount;
   let classes;
-  // const render = createClientRender({ strict: true });
+  const render = createClientRender({ strict: true });
   const defaultProps = {
-    renderInput: () => null,
+    renderInput: params => <TextField {...params} label="defaultProps" />,
   };
 
   before(() => {
@@ -18,10 +19,6 @@ describe('<Autocomplete />', () => {
     mount = createMount({ strict: true });
   });
 
-  after(() => {
-    cleanup();
-  });
-
   describeConformance(<Autocomplete {...defaultProps} />, () => ({
     classes,
     inheritComponent: 'div',
@@ -30,4 +27,26 @@ describe('<Autocomplete />', () => {
     testComponentPropWith: 'div',
     after: () => mount.cleanUp(),
   }));
+
+  describe('combobox', () => {
+    it('should clear the input when blur', () => {
+      const { container } = render(<Autocomplete {...defaultProps} />);
+      const input = container.querySelector('input');
+      input.focus();
+      fireEvent.change(input, { target: { value: 'a' } });
+      expect(input.value).to.equal('a');
+      document.activeElement.blur();
+      expect(input.value).to.equal('');
+    });
+  });
+
+  describe('multiple', () => {
+    it('should not crash', () => {
+      const { container } = render(<Autocomplete {...defaultProps} multiple />);
+      const input = container.querySelector('input');
+      input.focus();
+      document.activeElement.blur();
+      input.focus();
+    });
+  });
 });