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] Provide more details in the onChange event #19064

Closed
whuey1992 opened this issue Jan 3, 2020 · 14 comments · Fixed by #19959
Closed

[Autocomplete] Provide more details in the onChange event #19064

whuey1992 opened this issue Jan 3, 2020 · 14 comments · Fixed by #19959
Labels
component: autocomplete This is the name of the generic UI component, not the React module! good first issue Great for first contributions. Enable to learn the contribution process. new feature New feature or request

Comments

@whuey1992
Copy link

Is there a way to get only the option that was added/removed in the onChange event handler? Controlled auto complete shows this as their onChange handler:

onChange={(event, newValue) => {
          setValue(newValue);
        }}

So when i use this for multiple my previous state is [A,B] and newValue is [A,B,C]. I have to diff the previous state with next state to see what was added or removed. When I tried using event.target.value, the first onChange returns 0 then after gives me the correct value that changed.

@oliviertassinari oliviertassinari added new feature New feature or request component: autocomplete This is the name of the generic UI component, not the React module! and removed new feature New feature or request labels Jan 3, 2020
@oliviertassinari
Copy link
Member

@whuey1992 What's your use case?

@whuey1992
Copy link
Author

I am using two autocompletes for a form. When certain options are selected in one autocomplete, it requires options from the other autocomplete to be selected. Same with if those options are removed.

@whuey1992
Copy link
Author

When a new option is selected I can check the last element added to newValue but for deletion I need to do a diff of previous state vs newValue. Which is also kind of made more difficult to distinguish for clear

@oliviertassinari
Copy link
Member

oliviertassinari commented Jan 3, 2020

@whuey1992 Thanks for sharing. It's unclear what a good solution to the problem would be. I would propose that we wait for more feedback before taking action.

In your case, I don't understand what prevents you to use the final state to run the logic.

@whuey1992
Copy link
Author

It doesnt prevent me from running the logic, but it adds complexity to do a workaround (possibility of bugs) and isn't optimal O(AB) to do diffing.

@oliviertassinari
Copy link
Member

oliviertassinari commented Jan 11, 2020

@whuey1992 Ok thanks for the feedback. I'm closing. From what you are describing, you are trying to bridge a gab between a declarative world and an imperative one. I would suggest that you check how you could remove this imperative requirement.


I have added the waiting for users upvotes tag. I'm closing the issue as we are not sure people are looking for such abstraction. So please upvote this issue if you are. We will prioritize our effort based on the number of upvotes.

@oliviertassinari oliviertassinari added waiting for 👍 Waiting for upvotes new feature New feature or request labels Jan 11, 2020
@oliviertassinari
Copy link
Member

On a side note, the fix on our side would be easy. So it really boils down to encouraging stateless logic.

@oliviertassinari
Copy link
Member

I have changed my mind. It seems to be a feature frequently present in alternative projects. In #18976, we see a potential use case. So, in this context, I think that we should push it further.

@oliviertassinari oliviertassinari changed the title Controlled multiple autocomplete [Autocomplete] Provide more details in the onChange event Jan 11, 2020
@oliviertassinari
Copy link
Member

Maybe something like this?

diff --git a/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js b/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
index de0bfd292..091a1b549 100644
--- a/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
+++ b/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
@@ -418,35 +418,41 @@ export default function useAutocomplete(props) {
     }
   };

-  const handleValue = (event, newValue) => {
+  const handleValue = (event, newValue, reason, details) => {
     if (value === newValue) {
       return;
     }

     if (onChange) {
-      onChange(event, newValue);
+      onChange(event, newValue, reason, details);
     }

     setValue(newValue);
   };

-  const selectNewValue = (event, newValue) => {
+  const selectNewValue = (event, option, origin = 'options') => {
+    let reason = 'select-option';
+    let newValue = option;
+
     if (multiple) {
-      const item = newValue;
       newValue = Array.isArray(value) ? [...value] : [];

-      const itemIndex = findIndex(newValue, valueItem => getOptionSelected(item, valueItem));
+      const itemIndex = findIndex(newValue, valueItem => getOptionSelected(option, valueItem));

       if (itemIndex === -1) {
-        newValue.push(item);
-      } else {
+        newValue.push(option);
+      } else if (origin !== 'freeSolo') {
         newValue.splice(itemIndex, 1);
+        reason = 'remove-option';
       }
     }

     resetInputValue(event, newValue);

-    handleValue(event, newValue);
+    handleValue(event, newValue, reason, {
+      option,
+    });
+
     if (!disableCloseOnSelect) {
       handleClose(event);
     }
@@ -525,7 +531,7 @@ export default function useAutocomplete(props) {
       onInputChange(event, '', 'clear');
     }

-    handleValue(event, multiple ? [] : null);
+    handleValue(event, multiple ? [] : null, 'clear');
   };

   const handleKeyDown = other => event => {
@@ -597,7 +603,7 @@ export default function useAutocomplete(props) {
             // Allow people to add new values before they submit the form.
             event.preventDefault();
           }
-          selectNewValue(event, inputValue);
+          selectNewValue(event, inputValue, 'freeSolo');
         }
         break;
       case 'Escape':
@@ -620,7 +626,9 @@ export default function useAutocomplete(props) {
           const index = focusedTag === -1 ? value.length - 1 : focusedTag;
           const newValue = [...value];
           newValue.splice(index, 1);
-          handleValue(event, newValue);
+          handleValue(event, newValue, 'remove-option', {
+            option: value[index],
+          });
         }
         break;
       default:
@@ -649,7 +657,7 @@ export default function useAutocomplete(props) {
     }

     if (autoSelect && selectedIndexRef.current !== -1) {
-      handleValue(event, filteredOptions[selectedIndexRef.current]);
+      handleValue(event, filteredOptions[selectedIndexRef.current], 'blur');
     } else if (!freeSolo) {
       resetInputValue(event, value);
     }
@@ -674,7 +682,7 @@ export default function useAutocomplete(props) {
       }

       if (!disableClearable && !multiple) {
-        handleValue(event, null);
+        handleValue(event, null, 'clear');
       }
     } else {
       handleOpen(event);
@@ -710,7 +718,9 @@ export default function useAutocomplete(props) {
   const handleTagDelete = index => event => {
     const newValue = [...value];
     newValue.splice(index, 1);
-    handleValue(event, newValue);
+    handleValue(event, newValue, 'remove-option', {
+      option: value[index],
+    });
   };

   const handleListboxRef = useEventCallback(node => {
@@ -992,6 +1002,7 @@ useAutocomplete.propTypes = {
    *
    * @param {object} event The event source of the callback
    * @param {any} value
+   * @param {string} reason One of "input" (user input) or "reset" (programmatic change).
    */
   onChange: PropTypes.func,
   /**

@m4theushw
Copy link
Member

@oliviertassinari Should we add to the details of remove-option the method that was used to remove the option: backspace, tag-delete, option-click?

handleValue(event, newValue, 'remove-option', {
  option: value[index],
  method: 'backspace'
});

@oliviertassinari
Copy link
Member

oliviertassinari commented Jan 13, 2020

@m4theushw I don't know. I think that it's a tricky problem. I would suggest that we do it incrementally: expose the minimum of information possible and based on compelling use cases, broaden it. In this context, the use case seems to be really about gaining information on the individual option interacted with rather than the whole set of selected options. Now, because we frequently have a third "reason" argument, it makes sense to include it now and to add a 4th and final argument, with a rich object structure.
In the above proposal, we have 3 cases: add one option, remove one option, remove all options.

What do you think?

@m4theushw
Copy link
Member

@oliviertassinari No problem, keeping the 4th argument with only the removed option is good by now. If someone asks we can add more information to the details object in the future.

@whuey1992
Copy link
Author

@oliviertassinari looks good to me

@oliviertassinari oliviertassinari added the good first issue Great for first contributions. Enable to learn the contribution process. label Jan 18, 2020
@netochaves
Copy link
Contributor

If you guys don't mind i'll work on that.

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! good first issue Great for first contributions. Enable to learn the contribution process. new feature New feature or request
Projects
None yet
4 participants