downshift version: 1.18.0node version: 8.2.1npm (or yarn) version: 5.5.1Issues is probably not the best place for this, but I wanted to log if before I forget. Feel free to close this soon though and/or put it somewhere more appropriate.
I want to control the inputValue myself, so I started using the inputValue and onInputValueChange props. But I found a case where it didn't behave as expected. In my code, I save the initial value of the input, and regardless of what the user has entered since then, pressing Escape should reset the input to the initial value I've saved.
I implemented this (it's actually a common behavior across all my inputs, so it didn't take any extra work). The problem is that pressing Escape would clear out the input. Debugging it showed that pressing Escape would run my code which resets it to the initial value, but immediately after Downshift first onInputValueChange with an empty string, which clears it out.
Turns out Downshift resets the state when you press Escape, and part of that resetting is setting the input value to the selected value. But since I didn't provide a selectedItem prop, it resets it to an empty string.
The solution is to use the selectedItem prop instead of inputValue like this:
<Downshift
onChange={onUpdate}
selectedItem={value}
onInputValueChange={value => onUpdate(value)}
defaultHighlightedIndex={0}
>
This all works, but it feels asymmetric (why do I pass value down as selectedItem but I'm listening to the inputValue?). It's confusing exactly what the difference is between selectedItem and inputValue prop. I don't have enough awareness of what the difference actually is so I don't know what the solution is. Just wanted to give some feedback while I'm working with it.
Feel free to simply take this feedback and close this issue with nothing actionable.
Hi @jlongster!
I'm thrilled you're using downshift. I'm sorry and agree that inputValue and selectedValue are confusing. I'm going to describe things a little bit despite the fact that you probably understand lots of this just for the benefit of others and to make things more clear.
Here's a brief definition of the two props:
selectedItem: the current item that the user has selected. This happens when the user performs a "selection action" (click, or enter when an item is highlighted) on one of the items you render (with getItemProps()).inputValue: the value the user has typed, or the stringified version of selectedItem as defined by itemToString (defaults to casting the selectedItem as a string).Unlike regular <input />s, the more common use case for components like downshift is to not change the selection when the user types, but wait until the user makes a "selection action." In addition, the value that appears in the input is often a string-based representation of a more complex value (an object). Because of these qualities, we need to store the two states of selectedItem and inputValue separately.
The onInputValueChange (and the onStateChange) function simply calls your callback with what it suggests you update your state to based on downshift's semantics. Similar to how the onChange callback works on an <input />, they're simply letting you know what downshift would update the value to if it were in control of the state itself.
Now, to address the Escape key problem, you might try this... One of the lesser-known features of the way downshift composes your event handlers together is how it behaves differently when you call event.preventDefault(). For any events that are doing something you don't like, you can pass your own event handler and call event.preventDefault() and that will prevent downshift's default behavior as well.
You can see this in action here. And you can see the code for this here:
https://github.com/paypal/downshift/blob/0d78347a2a424f55c10f30eb77d52d2f7b76a017/src/utils.js#L127-L141
If you could make a codesandbox of your solution, I might be able to help improve the solution and it could reveal some areas of clarification we could make in the documentation.
Thanks again!
Because of these qualities, we need to store the two states of selectedItem and inputValue separately.
That makes sense, I had somewhat forgotten that because right now my items are strings so there's no difference between an item and it's "value". There's something not entirely clear if I need to be providing selectedItem or not though - apparently I can get away with not providing it at all, except then the "escape" issue happens and it seems like a small thing to make me also provide a selectedItem and handle that state.
The difference makes sense though. I think it's slightly confusing because if certain actions don't work if only providing inputValue, and you need to also provide selectedItem, is it OK to ever just provide inputValue? If only providing it breaks the escape behavior, is there a way to make that better? I don't know. The only thing I can think of is to encourage users to provide selectedItem and listen for onSelect instead of providing inputValue and listen for onInputValueChange, and derive the initial input value from the initial selectedItem. (Maybe it already works that way, since you already have itemToString?)
Overriding the events worked perfectly, thanks! I ended up overriding several of the shortcuts because I have custom behavior for enter, tab, etc and using preventDefault worked.
It's hard to me to extract this out, but if I have any future questions, I'll try to create an isolated case to show an example. I'll give you my full usage of downshift though, but it's probably hard to read, as right now I'm mostly focusing on getting it working. I tend to do things inline a lot and later pull stuff out and start abstracting it better. It's also using a slightly different component interface but you should able to read it. Behold: https://gist.github.com/jlongster/d41f8b1211f1249003722c593cd21acf
This has several behaviors:
shouldSaveFromKey, not shown here) moves away from the component and updates the field with the current highlighted itemBy the way, generally it was nice that this is super flexible and it was easy to make it do exactly what I wanted once I started controlling state and overriding events. Thanks for making it so flexible!
Glad you got it all working and I'm sorry some of it was a little confusing. I left a comment on your gist. I'm not sure there's anything we can do from here, so I'll go ahead and close this issue.
Thanks again for the feedback @jlongster :)
No problem! Let's continue the discussion there as I think it has helped clarify my original question (I've responded)
I've commented also (Dear GitHub, please add notifications to gists!)
Most helpful comment
Hi @jlongster!
I'm thrilled you're using downshift. I'm sorry and agree that
inputValueandselectedValueare confusing. I'm going to describe things a little bit despite the fact that you probably understand lots of this just for the benefit of others and to make things more clear.Here's a brief definition of the two props:
selectedItem: the current item that the user has selected. This happens when the user performs a "selection action" (click, or enter when an item is highlighted) on one of the items you render (withgetItemProps()).inputValue: the value the user has typed, or the stringified version ofselectedItemas defined byitemToString(defaults to casting theselectedItemas a string).Unlike regular
<input />s, the more common use case for components like downshift is to not change the selection when the user types, but wait until the user makes a "selection action." In addition, the value that appears in the input is often a string-based representation of a more complex value (an object). Because of these qualities, we need to store the two states ofselectedItemandinputValueseparately.The
onInputValueChange(and theonStateChange) function simply calls your callback with what it suggests you update your state to based on downshift's semantics. Similar to how theonChangecallback works on an<input />, they're simply letting you know what downshift would update the value to if it were in control of the state itself.Now, to address the
Escapekey problem, you might try this... One of the lesser-known features of the way downshift composes your event handlers together is how it behaves differently when you callevent.preventDefault(). For any events that are doing something you don't like, you can pass your own event handler and callevent.preventDefault()and that will prevent downshift's default behavior as well.You can see this in action here. And you can see the code for this here:
https://github.com/paypal/downshift/blob/0d78347a2a424f55c10f30eb77d52d2f7b76a017/src/utils.js#L127-L141
If you could make a codesandbox of your solution, I might be able to help improve the solution and it could reveal some areas of clarification we could make in the documentation.
Thanks again!