downshift-js/downshift

Setting the selectedItem to '' does not clear the inputValue

yp opened this issue · 12 comments

yp commented
  • downshift version: 1.16.0
  • node version:
  • npm (or yarn) version:

What you did:
I have a component that lists several items using several "Downshift-enriched" inputs. At the end of the list there is an empty Downshift-enriched input for adding a new item to the list. As such the components are semi-controlled, in the sense that the selectedItem property is set by the parent component.

What happened:
If I select a new item using the last input component I would expect that a new empty input component appears after the last one.
However, it appears a new input component where the input value is the one selected before.

Reproduction repository:
https://codesandbox.io/s/k5px1z0075
Select a fruit (apple for example) in the only input that appears and see that two components have the same fruit (instead of being one selected and the other one empty).

Problem description:
Maybe setting to null or '' the selectedItem prop does not correctly clear the internal state.

I'm not 100% certain, but I'm thinking that perhaps something's preventing the second autocomplete from re-rendering. Could you do a little more digging?

yp commented

I think that the component re-renders. Indeed the componentDidUpdate fires. However, inputValue is not cleared sing the selectedItem prop does not change (was '' before, it stays '' after).
If selectedItem is a controlled prop, does it make sense to clear/reset inputValue after a selection is made? As far as I can tell, the componentDidUpdate should then correctly set the inputValue to the string representation of selectedItem (if selectedItem changes).
It should be done inside the component, since I don't think there's an API for clearing inputValue.

If selectedItem is a controlled prop, does it make sense to clear/reset inputValue after a selection is made?

I think it makes more sense that inputValue should be updated anytime there's an update to selectedItem (and it should be updated by calling itemToString). I thought that's what happens. Could you figure out why that's not happening? Thanks!

yp commented

I think it is related to this line: https://github.com/paypal/downshift/blob/936c8e053dbb2be4b768ad0fd0b7bce21afb4e74/src/downshift.js#L241
in function selectItem.
It seems that Downshift assumes that item will be the "next" selected item, while, when selectedItem is a controlled prop, that assumption may not be true.

Ahhh, yeah, that's tricky... Hmmm... I'm not sure how to solve this honestly. A workaround for you could be to control the inputValue as well. I think that's probably the best course of action actually. Unless you have another suggestion.

yp commented

Yes, it is a possibility.
Another possibility is to set inputValue using the itemToString prop if selectedItem is not controlled or to '' otherwise. What do you think?

Yeah, I'd considered that. So now we need to make a decision between two "defaults" when you control the selectedItem. In a decision like this, we need to decide which is a more common scenario:

  1. I'm controlling selectedItem so I can set it externally, but I still want the user to change the selected item themselves and I want the input value to reflect that change.
  2. I'm controlling selectedItem so I can set it externally, and if the user changes the selected item, I don't want the item they selected to change the input value.

I'd say that scenario 1 is more common. So I think that we'd be better off leaving things as they are and forcing folks in scenario 2 to control the inputValue.

yp commented

I see. Please take all the rest of this comment as my respectful opinion.
For better consistency of the design, scenario 1 means that selectedItem is not really a controlled prop since it assumes that the next selectedItem will be exactly the one resulting from the selection. The solution I was proposing has the benefit that if the controlled prop selectedItem changes, then the inputValue will be updated in componentDidUpdate by this line: https://github.com/paypal/downshift/blob/936c8e053dbb2be4b768ad0fd0b7bce21afb4e74/src/downshift.js#L749
Hence the behavior perceived by the user is that of scenario 1, but without forcing to control the inputValue. (BTW, the current implementation resets the inputValue twice.)
Please close the issue if you prefer to leave things as-is.

Ah, so you're saying if we make the suggested change, we'll update the inputValue anyway because of componentDidUpdate?

yp commented

By code inspection, I would say yes. But I did not check.

If you would be willing, I would appreciate a pull request that demonstrates this approach in source, tests, and a new example. Could you do that? Thanks so much for iterating with me on this!

Just ran into this same problem, and happy to find you guys already figured it out! This is what I ❤️ about open-source. Thanks for all your hard work!