Repository: mui/material-ui (TypeScript)
Date: 2020-01-25 16:42:35+00:00
Task type: Feature

Issue:
[Autocomplete] Improve autoSelect logic
For AutoComplete, on Blur when multiple and freeSolo is set.

It will be nice that the value of the input field, validate, like when we hit Enter.

- [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.

## Summary 💡

On multiple and freeSolo, on blur the value in the input is automatically validated

## Motivation 🔦

When you have multiple input and one is auto complete, the user expected that when he leave the filed the value is taking care of.





Issue discussion:
@castroCrea I don't understand, could you provide more details?
I'm sorry for not been clear.

For example go on https://material-ui.com/components/autocomplete/#multiple-values the freeSolo one, 
enter a freeSolo value without hit Enter, then hit Tab
The value is not Saved, it doesn't show on onChange props.
The value of the input field is lost if in spite of Enter you hit Tab.

Me I use it, for email autocompletion, in email form, the user often wrote an email and hit tab to move on.
 
Is that more understandable ?
👋 Thanks for using Material-UI!

We use GitHub issues exclusively as a bug and feature requests tracker, however,
this issue appears to be a support request.

For support, please check out https://material-ui.com/getting-started/support/. Thanks!

If you have a question on StackOverflow, you are welcome to link to it here, it might help others.
If your issue is subsequently confirmed as a bug,  and the report follows the issue template, it can be reopened.
Thanks for the details. You can implement such logic with the exposed API.
Do you means by using Ref ?
I tried many thing until now, but can't make it work as autoComplete use the ref on the input, with out messing up with the rest of the component.

The simpler solution will be to had a keyDown event on Tab like Enter, no ?

So i think it's a feature and note really a support ?

https://github.com/mui-org/material-ui/blob/020b7469d9ef1b9b545a23a40565a2a9c2efc1fc/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js#L582
@castroCrea Looking closer, it seems that the auto-select logic is missing a few cases. Thanks for opening this issue.

1. The `autoSelect` + `multiple` props combination is broken, it throws
2. The `autoSelect` + `freeSolo` props combination is broken, it should auto-select, as it does on Gmail

What do you think of this patch (it would also fix `autoSelect` + `multiple` + `freeSolo`)?

```diff
diff --git a/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js b/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
index a0dcae57a..d0b0fc444 100644
--- a/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
+++ b/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
@@ -649,7 +649,9 @@ export default function useAutocomplete(props) {
     }

     if (autoSelect && selectedIndexRef.current !== -1) {
-      handleValue(event, filteredOptions[selectedIndexRef.current]);
+      selectNewValue(event, filteredOptions[selectedIndexRef.current]);
+    } else if (autoSelect && freeSolo && inputValue !== '') {
+      selectNewValue(event, inputValue, 'freeSolo');
     } else if (!freeSolo) {
       resetInputValue(event, value);
     }
```

Do you want to work on a pull request? (as well as the corresponding tests) :)
No Problem,
I don't dig into the useAutocomplete as far as that, but it seems good.

I don't have a lot of time now but I can work on the PR later. 

I couldn't find any test on autoComplete in the Test Folder.


@castroCrea Great. If you don't have a lot of time, the most important part would be to confirm that this patch solves your problem, then, maybe a future developer facing the same problem, we take on the task carry it to a stable release. For the test, you can find them in `Autocomplete/Autocomplete.test.js`.
@castroCrea if you don't mind I can work on it.
no I don't mind, actually it will be nice. I don't have time yet.
@castroCrea could you please tell me which versions of `@material-ui/core` and `@material-ui/lab` did you use?
I use 4.8.3, the last one
@castroCrea    
` "@material-ui/core": "4.8.3",
  "@material-ui/lab": "^4.0.0-alpha.39",`
?
yes exactly
it's there


https://github.com/mui-org/material-ui/tree/master/packages/material-ui/src/utils
@castroCrea I just want to make sure  I've understand the issue.
New value should be saved, if it is not empty, on blur event?
Yes for free solo and multiple 
@oliviertassinari your [solution](issuecomment-575596764) works if `autoSelect` is  `true` (it is `false` by default) . I think adding new flag is a bad idea since there already a lot of props. Can I just remove this flag from condition?  I'm not aware of the project, so it is hard to make decision


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
@@ -653,7 +653,9 @@ export default function useAutocomplete(props) {
     }
 
     if (autoSelect && selectedIndexRef.current !== -1) {
-      handleValue(event, filteredOptions[selectedIndexRef.current]);
+      selectNewValue(event, filteredOptions[selectedIndexRef.current]);
+    } else if (autoSelect && freeSolo && inputValue !== '') {
+      selectNewValue(event, inputValue, 'freeSolo');
     } else if (!freeSolo) {
       resetInputValue(event, value);
     }


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
@@ -29,10 +29,10 @@ describe('<Autocomplete />', () => {
 
   describe('combobox', () => {
     it('should clear the input when blur', () => {
-      const { container } = render(
+      const { getByRole } = render(
         <Autocomplete renderInput={params => <TextField {...params} />} />,
       );
-      const input = container.querySelector('input');
+      const input = getByRole('textbox');
       input.focus();
       fireEvent.change(document.activeElement, { target: { value: 'a' } });
       expect(input.value).to.equal('a');
@@ -49,12 +49,51 @@ describe('<Autocomplete />', () => {
     });
   });
 
-  describe('multiple', () => {
+  describe('prop: autoSelect', () => {
+    it('should add new value when autoSelect & multiple on blur', () => {
+      const handleChange = spy();
+      const options = ['one', 'two'];
+      render(
+        <Autocomplete
+          autoSelect
+          multiple
+          value={[options[0]]}
+          options={options}
+          onChange={handleChange}
+          renderInput={params => <TextField autoFocus {...params} />}
+        />,
+      );
+      fireEvent.change(document.activeElement, { target: { value: 't' } });
+      fireEvent.keyDown(document.activeElement, { key: 'ArrowDown' });
+      document.activeElement.blur();
+      expect(handleChange.callCount).to.equal(1);
+      expect(handleChange.args[0][1]).to.deep.equal(options);
+    });
+
+    it('should add new value when autoSelect & multiple & freeSolo on blur', () => {
+      const handleChange = spy();
+      render(
+        <Autocomplete
+          autoSelect
+          freeSolo
+          multiple
+          onChange={handleChange}
+          renderInput={params => <TextField autoFocus {...params} />}
+        />,
+      );
+      fireEvent.change(document.activeElement, { target: { value: 'a' } });
+      document.activeElement.blur();
+      expect(handleChange.callCount).to.equal(1);
+      expect(handleChange.args[0][1]).to.deep.equal(['a']);
+    });
+  });
+
+  describe('prop: multiple', () => {
     it('should not crash', () => {
-      const { container } = render(
+      const { getByRole } = render(
         <Autocomplete renderInput={params => <TextField {...params} />} multiple />,
       );
-      const input = container.querySelector('input');
+      const input = getByRole('textbox');
       input.focus();
       document.activeElement.blur();
       input.focus();
@@ -524,14 +563,15 @@ describe('<Autocomplete />', () => {
 
     describe('prop: disabled', () => {
       it('should disable the input', () => {
-        const { container } = render(
+        const { getByRole } = render(
           <Autocomplete
             disabled
             options={['one', 'two', 'three']}
             renderInput={params => <TextField {...params} />}
           />,
         );
-        expect(container.querySelector('input').disabled).to.be.true;
+        const input = getByRole('textbox');
+        expect(input.disabled).to.be.true;
       });
 
       it('should disable the popup button', () => {
@@ -645,14 +685,14 @@ describe('<Autocomplete />', () => {
 
     it('should not select undefined ', () => {
       const handleChange = spy();
-      const { container, getByRole } = render(
+      const { getByRole } = render(
         <Autocomplete
           onChange={handleChange}
           options={['one', 'two']}
           renderInput={params => <TextField {...params} />}
         />,
       );
-      const input = container.querySelector('input');
+      const input = getByRole('textbox');
       fireEvent.click(input);
 
       const listbox = getByRole('listbox');